#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
结果视图模块
显示SQL查询结果，支持表格和图表模式
"""

import logging
import copy
from typing import Dict, List, Any, Optional

from PySide6.QtCore import Qt, Signal, Slot, QSize, QModelIndex, QItemSelectionModel, QTimer, QSortFilterProxyModel, QStringListModel, QRegularExpression
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, 
                              QTableView, QAbstractItemView, QHeaderView,
                              QToolBar, QToolButton, QFileDialog,
                              QLabel, QComboBox, QCheckBox, QSpinBox,
                              QMessageBox, QMenu, QLineEdit, QPushButton,
                              QSplitter, QTabWidget, QStyledItemDelegate,
                              QTextEdit, QFrame, QCompleter, QDialog, QApplication)
from PySide6.QtGui import (QStandardItemModel, QStandardItem, QAction,
                          QColor, QFont, QKeySequence, QIcon, QPixmap,
                          QClipboard, QGuiApplication)

# 导入matplotlib相关模块
try:
    import matplotlib
    import matplotlib.pyplot as plt
    from matplotlib.patches import Rectangle, Wedge
    from matplotlib.lines import Line2D
    from matplotlib.collections import PathCollection
    from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar
    from matplotlib.figure import Figure
    HAS_MATPLOTLIB = True
except ImportError:
    HAS_MATPLOTLIB = False

from core.export_service import get_export_service
from core.i18n import tr


# 初始化日志记录器
logger = logging.getLogger("results_view")

# 分页配置
DEFAULT_PAGE_SIZE = 500  # 默认每页显示1000行
MAX_MEMORY_ROWS = 10000   # 内存中最多保留10000行数据


class NumericDelegate(QStyledItemDelegate):
    """数值单元格渲染代理"""
    
    def __init__(self, parent=None):
        """初始化代理
        
        Args:
            parent: 父对象
        """
        super().__init__(parent)
        
    def paint(self, painter, option, index):
        """绘制单元格
        
        Args:
            painter: 绘图器
            option: 样式选项
            index: 模型索引
        """
        # 右对齐数值
        option.displayAlignment = Qt.AlignRight | Qt.AlignVCenter
        
        # 调用默认实现
        super().paint(painter, option, index)


class DataFrameModel(QStandardItemModel):
    """数据模型，用于在表格视图中显示查询结果，支持分页和内存优化"""
    
    def __init__(self, parent=None):
        """初始化数据模型
        
        Args:
            parent: 父对象
        """
        super().__init__(parent)
        self._original_df = None  # 保存原始数据框引用
        self._columns = []
        self._column_types = []
        self._total_rows = 0
        self._page_size = DEFAULT_PAGE_SIZE
        self._current_page = 0
        self._cached_pages = {}  # 缓存已加载的页面
        self._max_cached_pages = 10  # 最多缓存10页
        self._filtered = False
        self._filter_text = ""
        self._filter_columns = None
        
    def set_dataframe(self, df):
        """设置数据
        
        Args:
            df: 数据对象，可以是duckdb的结果集或pandas DataFrame
        """
        try:
            # 保存原始数据框引用
            self._original_df = df
            
            # 获取列名
            self._columns = [col for col in df.columns]
            
            # 获取总行数（避免加载所有数据）
            if hasattr(df, '__len__'):
                self._total_rows = len(df)
            elif hasattr(df, 'shape'):
                self._total_rows = df.shape[0]
            else:
                # 对于DuckDB结果集，需要先获取行数
                try:
                    # 尝试获取行数而不加载所有数据
                    self._total_rows = df.shape[0] if hasattr(df, 'shape') else len(df.fetchall())
                    # 重新创建查询以避免消耗结果集
                    if hasattr(df, 'fetchall'):
                        logger.warning("无法获取行数而不消耗结果集，将使用分页加载")
                        self._total_rows = float('inf')  # 标记为未知大小
                except:
                    self._total_rows = float('inf')
            
            # 推断列类型（使用前几行样本）
            self._infer_column_types()
            
            # 清空缓存
            self._cached_pages.clear()
            self._current_page = 0
            self._filtered = False
            
            # 加载第一页数据
            self._load_page(0)
            
            logger.info(f"数据模型初始化完成，总行数: {self._total_rows}, 列数: {len(self._columns)}")
            
        except Exception as e:
            logger.error(f"设置数据框失败: {str(e)}")
            self.clear()
            
    def _infer_column_types(self):
        """推断列类型"""
        self._column_types = []
        
        try:
            # 获取前几行样本数据进行类型推断
            sample_data = self._get_sample_data(max_rows=10)
            
            for col_idx in range(len(self._columns)):
                col_type = self._infer_column_type_from_sample(sample_data, col_idx)
                self._column_types.append(col_type)
                
        except Exception as e:
            logger.error(f"推断列类型失败: {str(e)}")
            # 默认所有列为文本类型
            self._column_types = ['text'] * len(self._columns)
    
    def _get_sample_data(self, max_rows=10):
        """获取样本数据用于类型推断
        
        Args:
            max_rows: 最大样本行数
            
        Returns:
            样本数据列表
        """
        try:
            if hasattr(self._original_df, 'head'):
                # pandas DataFrame
                sample_df = self._original_df.head(max_rows)
                return sample_df.values.tolist()
            elif hasattr(self._original_df, 'iloc'):
                # 支持iloc的数据框
                sample_df = self._original_df.iloc[:max_rows]
                return sample_df.values.tolist()
            elif hasattr(self._original_df, 'values'):
                # pandas DataFrame (直接访问values)
                sample_data = self._original_df.values[:max_rows]
                return sample_data.tolist()
            elif hasattr(self._original_df, '__getitem__') and hasattr(self._original_df, '__len__'):
                # 支持索引的数据结构
                try:
                    sample_data = self._original_df[:max_rows]
                    if hasattr(sample_data, 'values'):
                        return sample_data.values.tolist()
                    else:
                        return list(sample_data)
                except Exception:
                    # 如果切片失败，尝试逐行获取
                    sample_data = []
                    for i in range(min(max_rows, len(self._original_df))):
                        try:
                            row = self._original_df[i]
                            if hasattr(row, 'values'):
                                sample_data.append(row.values.tolist())
                            else:
                                sample_data.append(list(row))
                        except Exception:
                            break
                    return sample_data
            else:
                # 对于其他类型（如DuckDB结果集）
                if hasattr(self._original_df, 'fetchmany'):
                    # DuckDB结果集
                    return self._original_df.fetchmany(max_rows)
                elif hasattr(self._original_df, '__iter__'):
                    # 可迭代对象
                    sample_data = []
                    for i, row in enumerate(self._original_df):
                        if i >= max_rows:
                            break
                        sample_data.append(row)
                    return sample_data
                else:
                    return []
        except Exception as e:
            logger.error(f"获取样本数据失败: {str(e)}")
            return []
    
    def _infer_column_type_from_sample(self, sample_data, col_idx):
        """从样本数据推断列类型
        
        Args:
            sample_data: 样本数据
            col_idx: 列索引
            
        Returns:
            列类型 ('numeric', 'text', 'bool', 'date', 'null')
        """
        if not sample_data or col_idx >= len(self._columns):
            return 'text'
            
        # 统计不同类型的数量
        numeric_count = 0
        bool_count = 0
        null_count = 0
        total_count = len(sample_data)
        
        for row in sample_data:
            if col_idx >= len(row):
                null_count += 1
                continue
                
            value = row[col_idx]
            
            if value is None:
                null_count += 1
            elif isinstance(value, (int, float)):
                numeric_count += 1
            elif isinstance(value, bool) or str(value).lower() in ('true', 'false'):
                bool_count += 1
                
        # 判断类型
        if null_count == total_count:
            return 'null'
        elif numeric_count > total_count * 0.7:  # 如果70%以上是数值
            return 'numeric'
        elif bool_count > total_count * 0.7:  # 如果70%以上是布尔值
            return 'bool'
            
        # 默认为文本
        return 'text'
    
    def _load_page(self, page_num):
        """加载指定页面的数据
        
        Args:
            page_num: 页面编号（从0开始）
        """
        if page_num in self._cached_pages:
            # 页面已缓存，直接使用
            self._current_page = page_num
            self._update_model_from_cache()
            return
            
        try:
            # 计算数据范围
            start_row = page_num * self._page_size
            end_row = start_row + self._page_size
            
            # 获取页面数据
            page_data = self._fetch_page_data(start_row, end_row)
            
            # 缓存页面数据
            self._cached_pages[page_num] = page_data
            
            # 限制缓存大小
            if len(self._cached_pages) > self._max_cached_pages:
                # 移除最旧的缓存页面
                oldest_page = min(self._cached_pages.keys())
                del self._cached_pages[oldest_page]
                
            self._current_page = page_num
            self._update_model_from_cache()
            
            logger.debug(f"加载页面 {page_num}，数据行数: {len(page_data)}")
            
        except Exception as e:
            logger.error(f"加载页面 {page_num} 失败: {str(e)}")
    
    def _fetch_page_data(self, start_row, end_row):
        """从原始数据源获取指定范围的数据
        
        Args:
            start_row: 起始行
            end_row: 结束行
            
        Returns:
            页面数据列表
        """
        try:
            if hasattr(self._original_df, 'iloc'):
                # pandas DataFrame
                page_df = self._original_df.iloc[start_row:end_row]
                return page_df.values.tolist()
            elif hasattr(self._original_df, 'values'):
                # pandas DataFrame (另一种方式)
                page_data = self._original_df.values[start_row:end_row]
                return page_data.tolist()
            elif hasattr(self._original_df, '__getitem__') and hasattr(self._original_df, '__len__'):
                # 支持索引的数据结构
                try:
                    page_data = self._original_df[start_row:end_row]
                    if hasattr(page_data, 'values'):
                        return page_data.values.tolist()
                    else:
                        return list(page_data)
                except Exception:
                    # 如果切片失败，尝试逐行获取
                    page_data = []
                    for i in range(start_row, min(end_row, len(self._original_df))):
                        try:
                            row = self._original_df.iloc[i] if hasattr(self._original_df, 'iloc') else self._original_df[i]
                            if hasattr(row, 'values'):
                                page_data.append(row.values.tolist())
                            else:
                                page_data.append(list(row))
                        except Exception:
                            break
                    return page_data
            else:
                # 对于其他类型（如DuckDB结果集），我们需要将其转换为可索引的格式
                logger.warning("数据源不支持随机访问，将转换为列表格式")
                
                # 如果还没有转换过，将整个结果集转换为列表
                if not hasattr(self, '_converted_data'):
                    if hasattr(self._original_df, 'fetchall'):
                        # DuckDB结果集
                        self._converted_data = self._original_df.fetchall()
                    elif hasattr(self._original_df, 'values'):
                        # DataFrame
                        self._converted_data = self._original_df.values.tolist()
                    else:
                        # 其他类型
                        self._converted_data = list(self._original_df)
                    
                    # 更新总行数
                    self._total_rows = len(self._converted_data)
                
                # 从转换后的数据中获取页面数据
                return self._converted_data[start_row:end_row]
                
        except Exception as e:
            logger.error(f"获取页面数据失败: {str(e)}")
            return []
    
    def _update_model_from_cache(self):
        """从缓存更新模型数据"""
        # 清空模型
        self.clear()
        
        if self._current_page not in self._cached_pages:
            return
            
        page_data = self._cached_pages[self._current_page]
        
        if not page_data:
            return
            
        # 设置表头
        self.setHorizontalHeaderLabels(self._columns)
        
        # 添加数据
        for row in page_data:
            items = []
            for col_idx, value in enumerate(row):
                # 确保值是字符串
                if value is None:
                    item = QStandardItem("NULL")
                    item.setForeground(QColor("#8a8a8a"))
                    item.setData(None, Qt.UserRole)
                else:
                    # 根据类型格式化显示
                    col_type = self._column_types[col_idx] if col_idx < len(self._column_types) else 'text'
                    
                    if col_type == 'numeric' and isinstance(value, (int, float)):
                        if isinstance(value, int):
                            display_text = str(value)
                        else:
                            display_text = f"{value:.6g}"  # 保留有效数字
                    elif col_type == 'bool':
                        if isinstance(value, bool):
                            display_text = str(value).lower()
                        else:
                            display_text = str(value)
                    else:
                        display_text = str(value)
                        
                    item = QStandardItem(display_text)
                    item.setData(value, Qt.UserRole)
                
                items.append(item)
            self.appendRow(items)
    
    def get_page_info(self):
        """获取分页信息
        
        Returns:
            (当前页, 总页数, 每页大小, 总行数)
        """
        if self._total_rows == float('inf'):
            total_pages = float('inf')
        else:
            total_pages = (self._total_rows + self._page_size - 1) // self._page_size
        
        return (self._current_page, total_pages, self._page_size, self._total_rows)
    
    def next_page(self):
        """下一页"""
        current_page, total_pages, _, _ = self.get_page_info()
        if current_page < total_pages - 1:
            self._load_page(current_page + 1)
            return True
        return False
    
    def previous_page(self):
        """上一页"""
        if self._current_page > 0:
            self._load_page(self._current_page - 1)
            return True
        return False
    
    def goto_page(self, page_num):
        """跳转到指定页面
        
        Args:
            page_num: 页面编号（从0开始）
        """
        current_page, total_pages, _, _ = self.get_page_info()
        if 0 <= page_num < total_pages:
            self._load_page(page_num)
            return True
        return False
    
    def set_page_size(self, page_size):
        """设置每页大小
        
        Args:
            page_size: 每页行数
        """
        if page_size > 0:
            self._page_size = page_size
            self._cached_pages.clear()  # 清空缓存
            self._load_page(0)  # 重新加载第一页
            
    def filter_dataframe(self, text: str, columns: Optional[List[str]] = None):
        """过滤数据
        
        Args:
            text: 过滤文本
            columns: 要过滤的列，如果为None则搜索所有列
        """
        # 注意：对于大数据集，过滤操作可能很慢
        # 这里只是简单实现，实际应用中可能需要在数据库层面进行过滤
        self._filter_text = text
        self._filter_columns = columns
        self._filtered = bool(text)
        
        if not text:
            # 清除过滤，重新加载当前页
            self._cached_pages.clear()
            self._load_page(self._current_page)
        else:
            # 应用过滤 - 简单实现，只过滤当前页
            logger.warning("当前过滤只应用于当前页面，建议在SQL查询中使用WHERE条件")
            self._apply_filter_to_current_page()
    
    def _apply_filter_to_current_page(self):
        """对当前页面应用过滤"""
        if self._current_page not in self._cached_pages:
            return
            
        page_data = self._cached_pages[self._current_page]
        filtered_data = []
        
        text = self._filter_text.lower()
        
        # 确定要搜索的列索引
        search_columns = []
        if self._filter_columns:
            for col in self._filter_columns:
                if col in self._columns:
                    search_columns.append(self._columns.index(col))
        else:
            search_columns = range(len(self._columns))
        
        # 过滤数据
        for row in page_data:
            for col_idx in search_columns:
                if col_idx < len(row):
                    cell_value = row[col_idx]
                    if cell_value is not None:
                        if text in str(cell_value).lower():
                            filtered_data.append(row)
                            break
        
        # 更新缓存
        self._cached_pages[self._current_page] = filtered_data
        self._update_model_from_cache()
    
    def sort_dataframe(self, column: int, order: Qt.SortOrder):
        """排序数据
        
        Args:
            column: 列索引
            order: 排序顺序
        """
        # 注意：对于大数据集，排序操作应该在数据库层面进行
        logger.warning("当前排序只应用于当前页面，建议在SQL查询中使用ORDER BY")
        
        if column < 0 or column >= len(self._columns):
            return
            
        if self._current_page not in self._cached_pages:
            return
            
        page_data = self._cached_pages[self._current_page]
        
        # 排序
        reverse = (order == Qt.DescendingOrder)
        
        def sort_key(row):
            value = row[column] if column < len(row) else None
            if value is None:
                return (1, "")  # None 值放在最后
            return (0, value)  # 非None值按原值排序
            
        page_data.sort(key=sort_key, reverse=reverse)
        
        # 更新缓存和模型
        self._cached_pages[self._current_page] = page_data
        self._update_model_from_cache()
        
    def get_dataframe(self):
        """获取当前页面数据
        
        Returns:
            当前页面数据
        """
        if self._current_page in self._cached_pages:
            return {
                "columns": self._columns, 
                "data": self._cached_pages[self._current_page], 
                "column_types": self._column_types
            }
        else:
            return {"columns": self._columns, "data": [], "column_types": self._column_types}
        
    def get_original_dataframe(self):
        """获取原始数据框引用
        
        Returns:
            原始数据框
        """
        return self._original_df
        
    def get_all_data(self):
        """获取所有数据用于导出
        
        Returns:
            包含所有数据的字典，格式为 {"columns": [...], "data": [...], "column_types": [...]}
        """
        try:
            if self._original_df is None:
                return {"columns": self._columns, "data": [], "column_types": self._column_types}
            
            # 转换为可导出的格式
            if hasattr(self._original_df, 'values'):
                # pandas DataFrame
                all_data = self._original_df.values.tolist()
            elif hasattr(self._original_df, '__len__') and hasattr(self._original_df, '__getitem__'):
                # 类似列表的数据结构
                all_data = list(self._original_df)
            else:
                # 其他类型，尝试转换
                if hasattr(self._original_df, 'fetchall'):
                    # DuckDB结果集 - 注意这会消耗结果集
                    all_data = self._original_df.fetchall()
                elif hasattr(self, '_converted_data') and hasattr(self._converted_data, '__iter__'):
                    # 如果已经转换过，使用转换后的数据
                    all_data = self._converted_data
                else:
                    # 尝试转换为列表
                    all_data = list(self._original_df)
            
            return {
                "columns": self._columns, 
                "data": all_data, 
                "column_types": self._column_types
            }
            
        except Exception as e:
            logger.error(f"获取所有数据失败: {str(e)}")
            return {"columns": self._columns, "data": [], "column_types": self._column_types}
    
    def is_filtered(self) -> bool:
        """检查数据是否已过滤
        
        Returns:
            是否已过滤
        """
        return self._filtered
        
    def row_count(self) -> int:
        """获取当前页面行数
        
        Returns:
            当前页面行数
        """
        if self._current_page in self._cached_pages:
            return len(self._cached_pages[self._current_page])
        return 0
        
    def column_count(self) -> int:
        """获取列数
        
        Returns:
            列数
        """
        return len(self._columns)
    
    def total_row_count(self) -> int:
        """获取总行数
        
        Returns:
            总行数
        """
        return self._total_rows if self._total_rows != float('inf') else 0


class FuzzySearchComboBox(QComboBox):
    """支持模糊搜索的下拉框"""
    
    def __init__(self, parent=None):
        """初始化
        
        Args:
            parent: 父对象
        """
        super().__init__(parent)
        self.setEditable(True)
        self.setInsertPolicy(QComboBox.NoInsert)
        self.setMaxVisibleItems(15)
        
        # 创建过滤代理模型
        self.proxy_model = QSortFilterProxyModel(self)
        self.proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.proxy_model.setFilterRole(Qt.DisplayRole)
        
        # 初始化原始模型
        self.string_model = QStringListModel(self)
        self.proxy_model.setSourceModel(self.string_model)
        
        # 创建自动完成器
        self.completer = QCompleter(self.proxy_model, self)
        self.completer.setCompletionMode(QCompleter.PopupCompletion)
        self.completer.setFilterMode(Qt.MatchContains)  # 包含搜索，而不是前缀搜索
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer.setMaxVisibleItems(15)
        self.setCompleter(self.completer)
        
        # 连接信号
        self.lineEdit().textEdited.connect(self._on_text_edited)
        self.completer.activated.connect(self._on_completer_activated)
    
    def setStringList(self, strings):
        """设置字符串列表
        
        Args:
            strings: 字符串列表
        """
        self.clear()
        self.string_model.setStringList(strings)
        for i, text in enumerate(strings):
            self.addItem(text)
    
    def _on_text_edited(self, text):
        """文本编辑处理
        
        Args:
            text: 编辑的文本
        """
        # 设置过滤器
        regex = QRegularExpression(text, QRegularExpression.CaseInsensitiveOption)
        self.proxy_model.setFilterRegularExpression(regex)
        self.completer.complete()  # 显示匹配结果
    
    def _on_completer_activated(self, text):
        """自动完成项被激活
        
        Args:
            text: 选中的文本
        """
        # 查找并设置对应的索引
        index = self.findText(text)
        if index >= 0:
            self.setCurrentIndex(index)
        

class ResultsView(QWidget):
    """查询结果视图组件"""
    
    # 信号定义
    export_requested = Signal(str, str)  # 文件路径, 格式
    
    def __init__(self, parent=None):
        """初始化结果视图组件
        
        Args:
            parent: 父窗口
        """
        super().__init__(parent)
        self.export_service = get_export_service()
        
        # 查询信息
        self.query_sql = None
        self.execution_time = None
        self.query_id = None
        
        # 初始化UI
        self._init_ui()
        
    def _init_ui(self):
        """初始化UI"""
        # 主布局
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(1)
        
        # 创建标签页
        self.tab_widget = QTabWidget()
        self.tab_widget.setDocumentMode(True)
        
        # 数据展示页
        self.data_tab = QWidget()
        self.data_tab_layout = QVBoxLayout(self.data_tab)
        self.data_tab_layout.setContentsMargins(0, 0, 0, 0)
        self.data_tab_layout.setSpacing(0)
        
        # 工具栏
        self.toolbar = QToolBar()
        self.toolbar.setIconSize(QSize(16, 16))
        
        # 导出按钮
        self.export_action = QAction(tr("results_export", "导出"), self)
        self.export_action.setIcon(QIcon.fromTheme("document-save"))
        self.export_action.setToolTip(tr("results_export_tooltip", "导出查询结果"))
        self.export_action.triggered.connect(self._show_export_dialog)
        self.toolbar.addAction(self.export_action)
        
        # 刷新按钮
        self.refresh_action = QAction(tr("results_refresh", "刷新"), self)
        self.refresh_action.setIcon(QIcon.fromTheme("view-refresh"))
        self.refresh_action.setToolTip(tr("results_refresh_tooltip", "刷新查询结果"))
        self.refresh_action.triggered.connect(self._refresh_view)
        self.toolbar.addAction(self.refresh_action)
        
        # 复制按钮
        self.copy_action = QAction(tr("results_copy", "复制"), self)
        self.copy_action.setIcon(QIcon.fromTheme("edit-copy"))
        self.copy_action.setToolTip(tr("results_copy_tooltip", "复制选中数据"))
        self.copy_action.setShortcut(QKeySequence.Copy)
        self.copy_action.triggered.connect(self._copy_selection)
        self.toolbar.addAction(self.copy_action)
        
        # 过滤控件
        # self.toolbar.addSeparator()
        # self.toolbar.addWidget(QLabel(tr("results_filter", "过滤: ")))
        #
        # self.filter_box = QLineEdit()
        # self.filter_box.setPlaceholderText(tr("results_filter_placeholder", "输入过滤条件..."))
        # self.filter_box.setClearButtonEnabled(True)
        # self.filter_box.setMinimumWidth(150)
        # self.filter_box.textChanged.connect(self._filter_changed)
        # self.toolbar.addWidget(self.filter_box)
        #
        # # 列选择下拉
        # self.column_combo = QComboBox()
        # self.column_combo.addItem(tr("results_all_columns", "所有列"))
        # self.column_combo.currentIndexChanged.connect(
        #     lambda: self._filter_changed(self.filter_box.text())
        # )
        # self.toolbar.addWidget(self.column_combo)
        
        # 结果统计标签
        self.toolbar.addSeparator()
        self.stats_label = QLabel(tr("results_stats_format", "{0} 行 x {1} 列").format(0, 0))
        self.toolbar.addWidget(self.stats_label)
        
        self.data_tab_layout.addWidget(self.toolbar)
        
        # 表格视图
        self.table_view = QTableView()
        self.table_view.setSelectionBehavior(QAbstractItemView.SelectItems)
        self.table_view.setAlternatingRowColors(True)
        self.table_view.setSortingEnabled(True)
        self.table_view.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
        self.table_view.horizontalHeader().setStretchLastSection(True)
        self.table_view.horizontalHeader().setSortIndicatorShown(True)
        self.table_view.verticalHeader().setDefaultSectionSize(32)  # 设置行高
        self.table_view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.table_view.customContextMenuRequested.connect(self._show_context_menu)
        

        # 创建数据模型
        self.model = DataFrameModel(self)
        self.table_view.setModel(self.model)
        
        # 设置数值代理
        self.numeric_delegate = NumericDelegate(self.table_view)
        
        self.data_tab_layout.addWidget(self.table_view)

        # 分页控件
        self.pagination_toolbar = QToolBar()
        self.pagination_toolbar.setIconSize(QSize(16, 16))

        # 上一页按钮
        self.prev_page_action = QAction(tr("results_prev_page", "◀ 上一页"), self)
        self.prev_page_action.setToolTip(tr("results_prev_page_tooltip", "上一页"))
        self.prev_page_action.triggered.connect(self._previous_page)
        self.pagination_toolbar.addAction(self.prev_page_action)

        # 页面信息标签
        self.page_info_label = QLabel(tr("results_page_of_total", "第 {0} 页，共 {1} 页").format(1, 1))
        self.pagination_toolbar.addWidget(self.page_info_label)

        # 下一页按钮
        self.next_page_action = QAction(tr("results_next_page", "下一页 ▶"), self)
        self.next_page_action.setToolTip(tr("results_next_page_tooltip", "下一页"))
        self.next_page_action.triggered.connect(self._next_page)
        self.pagination_toolbar.addAction(self.next_page_action)

        self.pagination_toolbar.addSeparator()

        # 跳转到页面
        self.pagination_toolbar.addWidget(QLabel(tr("results_goto_page", "跳转到: ")))
        self.page_spinbox = QSpinBox()
        self.page_spinbox.setMinimum(1)
        self.page_spinbox.setMaximum(1)
        self.page_spinbox.setValue(1)
        self.page_spinbox.setMinimumWidth(80)
        self.page_spinbox.valueChanged.connect(self._goto_page)
        self.pagination_toolbar.addWidget(self.page_spinbox)

        self.pagination_toolbar.addSeparator()

        # 每页大小设置
        self.pagination_toolbar.addWidget(QLabel(tr("results_page_size", "每页显示: ")))
        self.page_size_combo = QComboBox()
        self.page_size_combo.addItems(["100", "500", "1000", "2000", "5000"])
        self.page_size_combo.setCurrentText("500")
        self.page_size_combo.currentTextChanged.connect(self._change_page_size)
        self.pagination_toolbar.addWidget(self.page_size_combo)

        # 总行数显示
        self.pagination_toolbar.addSeparator()
        self.total_rows_label = QLabel(tr("results_total_rows_count", "总计: {0:,} 行").format(0))
        self.pagination_toolbar.addWidget(self.total_rows_label)
        
        # 性能提示
        self.performance_label = QLabel("")
        self.performance_label.setObjectName("performance_label")
        self.pagination_toolbar.addWidget(self.performance_label)
        
        self.data_tab_layout.addWidget(self.pagination_toolbar)
        
        # 添加到标签页
        self.tab_widget.addTab(self.data_tab, tr("results_tab_data", "数据"))
        
        # 图表页
        self.chart_tab = QWidget()
        self.chart_tab_layout = QVBoxLayout(self.chart_tab)
        self.chart_tab_layout.setContentsMargins(0, 0, 0, 0)
        
        # 图表工具栏
        chart_toolbar = QToolBar()
        chart_toolbar.setIconSize(QSize(16, 16))
        
        # 添加图表类型选择
        chart_toolbar.addWidget(QLabel(tr("chart_type", "图表类型: ")))
        self.chart_type_combo = QComboBox()
        self.chart_type_combo.addItems([
            tr("chart_bar", "柱状图"), 
            tr("chart_line", "折线图"), 
            tr("chart_pie", "饼图"), 
            tr("chart_scatter", "散点图"), 
            tr("chart_box", "箱线图"), 
            tr("chart_histogram", "直方图"), 
            tr("chart_heatmap", "热力图"), 
            tr("chart_area", "面积图")
        ])
        self.chart_type_combo.currentIndexChanged.connect(self._update_chart)
        chart_toolbar.addWidget(self.chart_type_combo)
        
        # X轴选择
        chart_toolbar.addSeparator()
        chart_toolbar.addWidget(QLabel(tr("chart_x_axis", "X轴: ")))
        self.x_axis_combo = FuzzySearchComboBox()
        self.x_axis_combo.setMinimumWidth(150)
        self.x_axis_combo.currentIndexChanged.connect(self._update_chart)
        chart_toolbar.addWidget(self.x_axis_combo)
        
        # Y轴选择
        chart_toolbar.addWidget(QLabel(tr("chart_y_axis", "Y轴: ")))
        self.y_axis_combo = FuzzySearchComboBox()
        self.y_axis_combo.setMinimumWidth(150)
        self.y_axis_combo.currentIndexChanged.connect(self._update_chart)
        chart_toolbar.addWidget(self.y_axis_combo)
        
        # 添加多系列支持
        chart_toolbar.addWidget(QLabel(tr("chart_secondary_y", "附加Y轴: ")))
        self.secondary_y_axis_combo = FuzzySearchComboBox()
        self.secondary_y_axis_combo.setMinimumWidth(150)
        self.secondary_y_axis_combo.currentIndexChanged.connect(self._update_chart)
        chart_toolbar.addWidget(self.secondary_y_axis_combo)
        
        # 刷新图表按钮
        self.refresh_chart_action = QAction(tr("chart_refresh", "刷新图表"), self)
        self.refresh_chart_action.setIcon(QIcon.fromTheme("view-refresh"))
        self.refresh_chart_action.triggered.connect(self._update_chart)
        chart_toolbar.addAction(self.refresh_chart_action)

        # 添加图例显示选项
        self.show_legend_check = QCheckBox(tr("chart_show_legend", "显示图例"))
        self.show_legend_check.setChecked(True)
        self.show_legend_check.stateChanged.connect(self._update_chart)
        chart_toolbar.addWidget(self.show_legend_check)
        
        # 添加网格显示选项
        self.show_grid_check = QCheckBox(tr("chart_show_grid", "显示网格"))
        self.show_grid_check.setChecked(True)
        self.show_grid_check.stateChanged.connect(self._update_chart)
        chart_toolbar.addWidget(self.show_grid_check)
        
        # 添加图表导出按钮
        self.export_chart_action = QAction(tr("chart_export", "导出图表"), self)
        self.export_chart_action.setIcon(QIcon.fromTheme("document-save"))
        self.export_chart_action.triggered.connect(self._export_chart)
        chart_toolbar.addAction(self.export_chart_action)
        
        self.chart_tab_layout.addWidget(chart_toolbar)
        
        
        # 图表容器
        self.chart_container = QWidget()
        self.chart_container_layout = QVBoxLayout(self.chart_container)
        self.chart_container_layout.setContentsMargins(10, 10, 10, 10)
        
        # 图表提示标签
        self.chart_placeholder = QLabel(tr("chart_placeholder", "选择列以创建图表"))
        self.chart_placeholder.setAlignment(Qt.AlignCenter)
        self.chart_container_layout.addWidget(self.chart_placeholder)
        
        self.chart_tab_layout.addWidget(self.chart_container)
        
        try:
            # 如果matplotlib已成功导入
            if HAS_MATPLOTLIB:
                # 创建图表
                self.figure = Figure(figsize=(5, 4), dpi=100)
                
                # 尝试使用支持缩放的画布
                try:
                    from ui.chart_canvas import ZoomableFigureCanvas
                    self.canvas = ZoomableFigureCanvas(self.figure, self.chart_container)
                    logger.info("使用支持Ctrl+滚轮缩放的图表画布")
                except ImportError as e:
                    logger.warning(f"无法导入缩放画布: {e}，使用默认画布")
                    self.canvas = FigureCanvas(self.figure)
                
                self.ax = self.figure.add_subplot(111)
                
                # 使用原生工具栏
                self.chart_toolbar_mpl = NavigationToolbar(self.canvas, self.chart_container)
                # 设置工具栏高度和样式
                self.chart_toolbar_mpl.setMaximumHeight(32)
                self.chart_toolbar_mpl.setStyleSheet("""
                    QToolBar {
                        border: none;
                        background-color: #f0f0f0;
                        spacing: 2px;
                        padding: 2px;
                        max-height: 32px;
                        min-height: 32px;
                    }
                    QToolBar QToolButton {
                        border: 1px solid transparent;
                        border-radius: 3px;
                        padding: 2px;
                        margin: 1px;
                        max-height: 24px;
                        min-height: 24px;
                    }
                    QToolBar QToolButton:hover {
                        border: 1px solid #c0c0c0;
                        background-color: #e0e0e0;
                    }
                    QToolBar QToolButton:pressed {
                        background-color: #d0d0d0;
                    }
                """)
                
                # 添加Ctrl+滚轮缩放提示到工具栏
                zoom_tip_label = QLabel("  💡 Ctrl+滚轮缩放")
                zoom_tip_label.setStyleSheet("""
                    QLabel {
                        color: #666666;
                        font-size: 11px;
                        padding: 2px 5px;
                        background-color: transparent;
                    }
                """)
                self.chart_toolbar_mpl.addWidget(zoom_tip_label)
                
                # 添加重置缩放按钮（仅对支持缩放的画布）
                if hasattr(self.canvas, 'reset_zoom'):
                    self.chart_toolbar_mpl.addSeparator()
                    reset_zoom_action = self.chart_toolbar_mpl.addAction("🔄 重置缩放")
                    reset_zoom_action.setToolTip("重置图表缩放到初始状态")
                    reset_zoom_action.triggered.connect(self.canvas.reset_zoom)
                
                # 添加点击事件处理
                self.canvas.mpl_connect('pick_event', self._on_chart_pick)
                
                # 替换占位符
                self.chart_container_layout.removeWidget(self.chart_placeholder)
                self.chart_placeholder.hide()
                self.chart_container_layout.addWidget(self.chart_toolbar_mpl)
                self.chart_container_layout.addWidget(self.canvas)
                
                self._has_matplotlib = True
            else:
                # 如果matplotlib不可用
                self._has_matplotlib = False
                self.chart_placeholder.setText(tr("chart_matplotlib_required", "需要安装matplotlib才能使用图表功能"))
        except Exception as e:
            # 处理任何其他错误
            logger.error(f"初始化matplotlib图表时出错: {str(e)}")
            self._has_matplotlib = False
            self.chart_placeholder.setText(f"{tr('chart_init_failed', '初始化图表失败')}: {str(e)}")
        
        self.tab_widget.addTab(self.chart_tab, tr("results_tab_chart", "图表"))
        
        # 查询信息页
        self.info_tab = QWidget()
        self.info_tab_layout = QVBoxLayout(self.info_tab)
        self.info_tab_layout.setContentsMargins(10, 10, 10, 10)
        
        # 添加查询执行摘要区域
        summary_frame = QFrame()
        summary_frame.setObjectName("summary_frame")
        summary_frame.setFrameShape(QFrame.StyledPanel)
        summary_layout = QVBoxLayout(summary_frame)
        
        # 执行时间显示
        time_layout = QHBoxLayout()
        self.execution_time_label = QLabel(f"{tr('query_execution_time', '执行时间')}: {tr('query_status_unknown', '未知')}")
        self.execution_time_label.setObjectName("execution_time_label")
        time_layout.addWidget(self.execution_time_label)
        
        # 添加结果行数
        self.row_count_label = QLabel(f"{tr('query_result_rows', '结果行数')}: 0")
        time_layout.addWidget(self.row_count_label)
        
        # 添加结果列数
        self.column_count_label = QLabel(f"{tr('query_result_columns', '结果列数')}: 0")
        time_layout.addWidget(self.column_count_label)
        
        # 添加状态标签
        self.query_status_label = QLabel(f"{tr('query_status', '状态')}: {tr('query_status_unknown', '未知')}")
        self.query_status_label.setObjectName("query_status_label")
        time_layout.addWidget(self.query_status_label)
        
        time_layout.addStretch(1)
        summary_layout.addLayout(time_layout)
        
        # 添加执行的SQL查询显示
        self.sql_group_label = QLabel(tr("query_sql_executed", "执行的SQL查询:"))
        self.sql_group_label.setObjectName("sql_group_label")
        summary_layout.addWidget(self.sql_group_label)
        
        self.sql_text = QTextEdit()
        self.sql_text.setObjectName("sql_text")
        self.sql_text.setReadOnly(True)
        self.sql_text.setFont(QFont("Consolas, 'Courier New', monospace", 10))
        self.sql_text.setMinimumHeight(100)
        self.sql_text.setMaximumHeight(150)
        summary_layout.addWidget(self.sql_text)
        
        # 添加复制SQL按钮
        copy_button = QPushButton(tr("query_copy_sql", "复制SQL"))
        copy_button.setMaximumWidth(100)
        copy_button.clicked.connect(self._copy_sql)
        summary_layout.addWidget(copy_button, 0, Qt.AlignRight)
        
        self.info_tab_layout.addWidget(summary_frame)
        
        # 结果集详细信息标签
        self.detail_label = QLabel(tr("query_result_details", "结果集详细信息:"))
        self.detail_label.setObjectName("detail_label")
        self.info_tab_layout.addWidget(self.detail_label)
        
        # 创建信息表格
        self.info_table = QTableView()
        self.info_table.setAlternatingRowColors(True)
        self.info_table.horizontalHeader().setStretchLastSection(True)
        self.info_table.verticalHeader().setVisible(False)
        self.info_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
        
        # 创建信息数据模型
        self.info_model = QStandardItemModel(0, 2, self)
        self.info_model.setHorizontalHeaderLabels([tr("query_info_property", "属性"), tr("query_info_value", "值")])
        self.info_table.setModel(self.info_model)
        
        # 添加表格到布局
        self.info_tab_layout.addWidget(self.info_table)
        
        self.tab_widget.addTab(self.info_tab, tr("results_tab_info", "查询信息"))
        
        layout.addWidget(self.tab_widget)
        
        # 连接信号
        self.model.modelReset.connect(self._update_stats)
        # self.model.modelReset.connect(self._update_column_combo)
        self.model.modelReset.connect(self._resize_columns)
        
    def display_result(self, df, query_sql=None, execution_time=None):
        """显示查询结果
        
        Args:
            df: 查询结果数据
            query_sql: 执行的SQL查询
            execution_time: 查询执行时间（秒）
        """
        if df is None:
            self.model.clear()
            self.stats_label.setText(tr("query_no_data", "无数据"))
            self._update_pagination_controls()
            return
            
        # 保存查询信息
        self.query_sql = query_sql
        self.execution_time = execution_time
            
        # 设置数据模型
        self.model.set_dataframe(df)
        
        # 更新分页控件
        self._update_pagination_controls()
        
        # 更新列下拉
        # self._update_column_combo()
        
        # 自动调整列宽
        self._resize_columns()
        
        # 更新图表的列选择
        self._update_chart_column_combo()
        
        # 更新查询信息
        if query_sql:
            self._update_query_info(query_sql, execution_time)
        
        # 切换到数据标签页
        self.tab_widget.setCurrentIndex(0)
        
        # 使用类型代理
        self._apply_column_delegates()
        
        # 更新统计信息
        self._update_stats()

    def _update_pagination_controls(self):
        """更新分页控件状态"""
        current_page, total_pages, page_size, total_rows = self.model.get_page_info()

        # 更新页面信息标签
        if total_pages == float('inf'):
            self.page_info_label.setText(tr("results_current_page", "第 {0} 页").format(current_page + 1))
        else:
            self.page_info_label.setText(
                tr("results_page_of_total", "第 {0} 页，共 {1} 页").format(current_page + 1, total_pages))

        # 更新总行数标签
        if total_rows == float('inf'):
            self.total_rows_label.setText(tr("results_total_unknown", "总计: 未知"))
        else:
            self.total_rows_label.setText(tr("results_total_rows_count", "总计: {0:,} 行").format(total_rows))

        # 更新页面跳转控件
        self.page_spinbox.blockSignals(True)
        self.page_spinbox.setValue(current_page + 1)
        if total_pages != float('inf'):
            self.page_spinbox.setMaximum(max(1, total_pages))
        else:
            self.page_spinbox.setMaximum(999999)  # 设置一个大的最大值
        self.page_spinbox.blockSignals(False)

        # 更新按钮状态
        self.prev_page_action.setEnabled(current_page > 0)
        if total_pages != float('inf'):
            self.next_page_action.setEnabled(current_page < total_pages - 1)
        else:
            self.next_page_action.setEnabled(True)  # 对于未知大小的数据，总是允许尝试下一页

        # 显示性能提示
        if total_rows > 10000:
            self.performance_label.setText(tr("results_large_dataset", "大数据集已启用分页优化"))
        elif total_rows > 50000:
            self.performance_label.setText(tr("results_huge_dataset", "超大数据集，建议使用WHERE条件过滤"))
        else:
            self.performance_label.setText("")
    
    def _previous_page(self):
        """上一页"""
        if self.model.previous_page():
            self._update_pagination_controls()
            self._update_stats()
            self._resize_columns()
    
    def _next_page(self):
        """下一页"""
        if self.model.next_page():
            self._update_pagination_controls()
            self._update_stats()
            self._resize_columns()
        else:
            # 如果没有更多页面，显示提示
            QMessageBox.information(self, "提示", "已经是最后一页了")
            
    def _goto_page(self, page_num):
        """跳转到指定页面"""
        # page_num 是从1开始的，需要转换为从0开始
        if self.model.goto_page(page_num - 1):
            self._update_pagination_controls()
            self._update_stats()
            self._resize_columns()
        else:
            # 恢复到当前页面
            current_page, _, _, _ = self.model.get_page_info()
            self.page_spinbox.blockSignals(True)
            self.page_spinbox.setValue(current_page + 1)
            self.page_spinbox.blockSignals(False)
            QMessageBox.warning(self, "错误", f"无法跳转到第 {page_num} 页")
    
    def _change_page_size(self, size_text):
        """改变每页大小"""
        try:
            page_size = int(size_text)
            self.model.set_page_size(page_size)
            self._update_pagination_controls()
            self._update_stats()
            self._resize_columns()
        except ValueError:
            QMessageBox.warning(self, "错误", f"无效的页面大小: {size_text}")
    
    def _update_stats(self):
        """更新统计信息"""
        current_page_rows = self.model.row_count()
        col_count = self.model.column_count()
        total_rows = self.model.total_row_count()
        
        if self.model.is_filtered():
            # 对于过滤后的数据，显示当前页面的行数
            self.stats_label.setText(f"当前页: {current_page_rows} 行 x {col_count} 列 (已过滤)")
        else:
            # 显示当前页面和总数信息
            if total_rows > 0:
                current_page, total_pages, page_size, _ = self.model.get_page_info()
                start_row = current_page * page_size + 1
                end_row = min(start_row + current_page_rows - 1, total_rows)
                self.stats_label.setText(f"显示 {start_row}-{end_row} 行，共 {total_rows:,} 行 x {col_count} 列")
            else:
                self.stats_label.setText(f"{current_page_rows} 行 x {col_count} 列")
    
    def _update_column_combo(self):
        """更新列选择下拉框"""
        self.column_combo.blockSignals(True)
        self.column_combo.clear()
        self.column_combo.addItem(tr("results_all_columns", "所有列"))
        
        for column in self.model._columns:
            self.column_combo.addItem(column)
            
        self.column_combo.blockSignals(False)
        
    def _apply_column_delegates(self):
        """应用列代理"""
        for col_idx, col_type in enumerate(self.model._column_types):
            if col_type == 'numeric':
                self.table_view.setItemDelegateForColumn(col_idx, self.numeric_delegate)
            else:
                self.table_view.setItemDelegateForColumn(col_idx, None)
        
    def _resize_columns(self):
        """自动调整列宽"""
        if self.model.columnCount() == 0:
            return
            
        # 自动调整所有列宽
        self.table_view.horizontalHeader().resizeSections(QHeaderView.ResizeToContents)
        
        # 限制最大列宽
        for col in range(self.model.columnCount()):
            curr_width = self.table_view.horizontalHeader().sectionSize(col)
            if curr_width > 300:
                self.table_view.horizontalHeader().resizeSection(col, 300)
                
    def _filter_changed(self, text: str):
        """过滤变化处理
        
        Args:
            text: 过滤文本
        """
        columns = None
        
        # 如果选择了特定列
        if self.column_combo.currentIndex() > 0:
            columns = [self.column_combo.currentText()]
            
        # 应用过滤
        self.model.filter_dataframe(text, columns)
        
    def _refresh_view(self):
        """刷新视图"""
        # 重新设置当前数据
        current_data = self.model.get_dataframe()
        
        # 清除过滤
        self.filter_box.clear()
        
        # 重新显示
        df = {"columns": current_data["columns"], "data": current_data["data"]}
        self.display_result(df)
        
    def _copy_selection(self):
        """复制选中数据"""
        selection = self.table_view.selectionModel()
        selected_indexes = selection.selectedIndexes()
        
        if not selected_indexes:
            return
            
        # 按行列组织选中的单元格
        rows = {}
        for index in selected_indexes:
            row = index.row()
            col = index.column()
            
            if row not in rows:
                rows[row] = {}
                
            # 获取实际值而不是显示值
            value = self.model.data(index, Qt.DisplayRole)
            if index.data(Qt.UserRole) is not None:
                value = index.data(Qt.UserRole)
                
            rows[row][col] = value
            
        # 格式化为制表符分隔的文本
        text = ""
        for row in sorted(rows.keys()):
            row_text = ""
            for col in sorted(rows[row].keys()):
                if row_text:
                    row_text += "\t"
                cell_value = rows[row][col]
                row_text += str(cell_value) if cell_value is not None else "NULL"
            text += row_text + "\n"
            
        # 复制到剪贴板
        clipboard = QGuiApplication.clipboard()
        clipboard.setText(text)
        
        # 显示状态提示
        count = len(selected_indexes)
        single_cell = count == 1
        msg = f"已复制{'单元格内容' if single_cell else f'{count} 个单元格'}"
        QMessageBox.information(self, "复制成功", msg)
        
    def _show_export_dialog(self):
        """显示导出对话框"""
        if self.model.row_count() == 0:
            QMessageBox.information(self, "无数据", "没有可导出的数据")
            return
            
        # 获取总行数
        total_rows = self.model.total_row_count()
        
        # Excel最大行数限制
        EXCEL_MAX_ROWS = 1048576
        
        # 检查是否超出Excel限制
        excel_warning = ""
        if total_rows > EXCEL_MAX_ROWS:
            excel_warning = f"\n\n⚠️ 警告：数据总行数为 {total_rows:,} 行，超出了Excel的最大行数限制（{EXCEL_MAX_ROWS:,} 行）。\n如果选择Excel格式导出，只能导出前 {EXCEL_MAX_ROWS:,} 行数据。\n建议使用CSV格式导出全部数据。"
        
        # 显示数据量信息
        info_msg = f"即将导出查询的所有数据：{total_rows:,} 行 x {self.model.column_count()} 列{excel_warning}"
        
        # 如果数据量很大，询问用户是否确认
        if total_rows > 100000:  # 超过10万行时提醒
            reply = QMessageBox.question(
                    self,
                "确认导出", 
                f"{info_msg}\n\n数据量较大，导出将在后台进行。是否继续？",
                QMessageBox.Yes | QMessageBox.No,
                QMessageBox.No
            )
            if reply != QMessageBox.Yes:
                return
        elif excel_warning:
            # 如果有Excel警告但数据量不是很大，也要提醒
            reply = QMessageBox.question(
                self, 
                "Excel限制警告", 
                f"{info_msg}\n\n是否继续导出？",
                QMessageBox.Yes | QMessageBox.No,
                QMessageBox.Yes
            )
            if reply != QMessageBox.Yes:
                return
            
        dialog = QFileDialog(self, "导出数据")
        dialog.setAcceptMode(QFileDialog.AcceptSave)
        dialog.setFileMode(QFileDialog.AnyFile)
        
        # 设置过滤器 - 只支持CSV、Excel和JSON
        filters = [
            "CSV文件 (*.csv)",
            "Excel文件 (*.xlsx)",
            "JSON文件 (*.json)",
            "所有文件 (*.*)"
        ]
        dialog.setNameFilters(filters)
        
        # 如果数据超出Excel限制，默认选择CSV
        if total_rows > EXCEL_MAX_ROWS:
            dialog.selectNameFilter("CSV文件 (*.csv)")
        
        # 执行对话框
        if dialog.exec_():
            file_paths = dialog.selectedFiles()
            if file_paths:
                file_path = file_paths[0]
                
                # 确定导出格式
                selected_filter = dialog.selectedNameFilter()
                if "CSV" in selected_filter:
                    fmt = "csv"
                elif "Excel" in selected_filter:
                    fmt = "excel"
                    # 再次检查Excel限制
                    if total_rows > EXCEL_MAX_ROWS:
                        reply = QMessageBox.warning(
                            self, 
                            "Excel限制", 
                            f"数据行数（{total_rows:,}）超出Excel最大限制（{EXCEL_MAX_ROWS:,}），\n只能导出前 {EXCEL_MAX_ROWS:,} 行。\n\n是否继续？建议改用CSV格式。",
                            QMessageBox.Yes | QMessageBox.No,
                            QMessageBox.No
                        )
                        if reply != QMessageBox.Yes:
                            return
                elif "JSON" in selected_filter:
                    fmt = "json"
                else:
                    # 根据文件扩展名判断
                    ext = file_path.split(".")[-1].lower()
                    if ext == "csv":
                        fmt = "csv"
                    elif ext in ("xls", "xlsx"):
                        fmt = "excel"
                        # 检查Excel限制
                        if total_rows > EXCEL_MAX_ROWS:
                            reply = QMessageBox.warning(
                                self, 
                                "Excel限制", 
                                f"数据行数（{total_rows:,}）超出Excel最大限制（{EXCEL_MAX_ROWS:,}），\n只能导出前 {EXCEL_MAX_ROWS:,} 行。\n\n是否继续？",
                                QMessageBox.Yes | QMessageBox.No,
                                QMessageBox.No
                            )
                            if reply != QMessageBox.Yes:
                                return
                    elif ext == "json":
                        fmt = "json"
                    else:
                        fmt = "csv"  # 默认CSV
                
                # 使用后台导出管理器
                try:
                    # 获取所有数据
                    all_data_dict = self.model.get_all_data()
                    
                    if not all_data_dict["data"]:
                        QMessageBox.warning(self, "导出失败", "无法获取数据或数据为空")
                        return
                    
                    # 导入导出管理器
                    from core.export_manager import get_export_manager
                    export_manager = get_export_manager()
                    
                    # 连接导出完成信号（如果还没有连接的话）
                    if not hasattr(self, '_export_signal_connected'):
                        export_manager.task_completed.connect(self._on_export_completed)
                        self._export_signal_connected = True
                    
                    # 创建导出任务
                    data_info = f"{total_rows:,} 行 x {self.model.column_count()} 列"
                    task_id = export_manager.create_export_task(
                        file_path=file_path,
                        format_type=fmt,
                        data=all_data_dict["data"],
                        columns=all_data_dict["columns"],
                        data_info=data_info
                    )
                    
                    # 启动任务
                    success = export_manager.start_task(task_id)

                        
                except Exception as e:
                            QMessageBox.critical(self, "导出失败", f"创建导出任务时发生错误: {str(e)}")
                            logger.error(f"创建导出任务失败: {str(e)}")
    
    def _on_export_completed(self, task_id: str, success: bool, message: str):
        """导出完成处理
        
        Args:
            task_id: 任务ID
            success: 是否成功
            message: 消息
        """
        # 导入通知模块
        from ui.widgets.notification import show_export_success_notification, show_error_notification
        
        if success:
            # 从导出管理器获取任务信息
            from core.export_manager import get_export_manager
            export_manager = get_export_manager()
            task = export_manager.get_task(task_id)
            
            if task:
                # 显示成功通知
                show_export_success_notification(
                    file_path=task.file_path,
                    record_count=task.total_rows
                )
            else:
                # 如果无法获取任务信息，显示基本成功通知
                show_export_success_notification()
        else:
            # 显示错误通知
            show_error_notification("导出失败", message)
    
    def _show_context_menu(self, position):
        """显示上下文菜单
        
        Args:
            position: 位置
        """
        if self.model.row_count() == 0:
            return
            
        menu = QMenu(self)
        
        # 复制选中
        copy_action = menu.addAction("复制选中")
        copy_action.triggered.connect(self._copy_selection)
        
        # 复制整行
        index = self.table_view.indexAt(position)
        if index.isValid():
            copy_row_action = menu.addAction("复制整行")
            copy_row_action.triggered.connect(lambda: self._copy_row(index.row()))
            
            # 复制单元格
            copy_cell_action = menu.addAction("复制单元格")
            copy_cell_action.triggered.connect(lambda: self._copy_cell(index))
            
        menu.addSeparator()
        
        # 导出
        export_action = menu.addAction("导出...")
        export_action.triggered.connect(self._show_export_dialog)
        
        menu.exec_(self.table_view.viewport().mapToGlobal(position))
        
    def _copy_row(self, row: int):
        """复制整行
        
        Args:
            row: 行索引
        """
        if row < 0 or row >= self.model.row_count():
            return
            
        # 获取行数据
        row_data = []
        for col in range(self.model.columnCount()):
            value = self.model.get_cell_value(row, col)
            row_data.append(str(value) if value is not None else "NULL")
            
        # 复制到剪贴板
        clipboard = QGuiApplication.clipboard()
        clipboard.setText("\t".join(row_data))
        
        # 提示
        QMessageBox.information(self, "复制成功", f"已复制第 {row+1} 行")
        
    def _copy_cell(self, index: QModelIndex):
        """复制单元格
        
        Args:
            index: 单元格索引
        """
        if not index.isValid():
            return
            
        # 获取单元格值
        value = self.model.get_cell_value(index.row(), index.column())
        
        # 复制到剪贴板
        clipboard = QGuiApplication.clipboard()
        clipboard.setText(str(value) if value is not None else "NULL")
        
        # 提示
        QMessageBox.information(self, "复制成功", "已复制单元格内容")

    def cleanup(self):
        """清理资源
        
        在关闭标签页或窗口时释放资源
        """
        try:
            # 清除表格模型数据
            if hasattr(self, "table_view") and self.table_view:
                if self.table_view.model():
                    self.table_view.setModel(None)
            
            # 停止所有自定义定时器(如果有)
            for child in self.findChildren(QTimer):
                child.stop()
            
            # 清除任何大数据对象的引用
            if hasattr(self, 'current_data'):
                self.current_data = None
                
            if hasattr(self, 'current_result'):
                self.current_result = None
            
            # 清理matplotlib图表
            if hasattr(self, 'ax') and self.ax:
                self.ax.clear()
                
            if hasattr(self, 'canvas') and self.canvas:
                self.canvas.draw()
                
            # 清理查询信息
            if hasattr(self, 'info_model') and self.info_model:
                self.info_model.setRowCount(0)
                
            if hasattr(self, 'sql_text') and self.sql_text:
                self.sql_text.clear()
            
        except Exception as e:
            logger.error(f"清理结果视图资源时出错: {str(e)}")

    def _update_chart_column_combo(self):
        """更新图表的列选择下拉框"""
        # 暂时阻断信号以避免在更新过程中触发图表更新
        self.x_axis_combo.blockSignals(True)
        self.y_axis_combo.blockSignals(True)
        self.secondary_y_axis_combo.blockSignals(True)
        
        # 保存当前选择
        x_current = self.x_axis_combo.currentText() if self.x_axis_combo.count() > 0 else ""
        y_current = self.y_axis_combo.currentText() if self.y_axis_combo.count() > 0 else ""
        secondary_y_current = self.secondary_y_axis_combo.currentText() if self.secondary_y_axis_combo.count() > 0 else ""
        
        # 清空当前选项
        self.x_axis_combo.clear()
        self.y_axis_combo.clear()
        self.secondary_y_axis_combo.clear()
        
        # 获取列名和类型信息
        columns = []
        column_types = []
        numeric_columns = []
        
        for i in range(self.model.columnCount()):
            column_name = self.model.headerData(i, Qt.Horizontal)
            column_type = "未知"
            if i < len(self.model._column_types):
                column_type = self.model._column_types[i]
                
            if column_name:
                columns.append(column_name)
                column_types.append(column_type)
                
                # 记录数值类型列，这些列适合用作Y轴
                if column_type == 'numeric':
                    numeric_columns.append(column_name)
        
        # 准备X轴和Y轴的数据
        x_axis_data = []
        y_axis_data = []
        
        # 准备X轴数据 - 所有列
        for i, column in enumerate(columns):
            x_axis_data.append(column)
            
        # 准备Y轴数据 - 优先显示数值列
        sorted_columns = []
        # 先添加数值类型的列
        sorted_columns.extend(numeric_columns)
        # 再添加其他类型的列
        for col in columns:
            if col not in numeric_columns:
                sorted_columns.append(col)
                
        # 设置模型数据
        self.x_axis_combo.setStringList(x_axis_data)
        self.y_axis_combo.setStringList(sorted_columns)
        self.secondary_y_axis_combo.setStringList(["无"] + sorted_columns)
        
        # 为下拉框项添加工具提示和样式
        for i, column in enumerate(columns):
            self.x_axis_combo.setItemData(i, f"类型: {column_types[i]}", Qt.ToolTipRole)
            
        for i, column in enumerate(sorted_columns):
            # 计算原始索引以获取正确的列类型
            orig_idx = columns.index(column)
            self.y_axis_combo.setItemData(i, f"类型: {column_types[orig_idx]}", Qt.ToolTipRole)
            
            # 为非数值列添加特殊样式，提示这些列可能不适合作为Y轴
            if column not in numeric_columns:
                self.y_axis_combo.setItemData(i, QColor("#FFA07A"), Qt.BackgroundRole)  # 浅珊瑚色背景
                self.y_axis_combo.setItemData(i, "注意: 此列不是数值类型，可能不适合作为Y轴", Qt.ToolTipRole)
        
        # 为secondary_y_axis_combo添加工具提示和样式
        self.secondary_y_axis_combo.setItemData(0, "不使用附加Y轴", Qt.ToolTipRole)
        for i, column in enumerate(sorted_columns):
            # 计算原始索引以获取正确的列类型
            orig_idx = columns.index(column)
            self.secondary_y_axis_combo.setItemData(i+1, f"类型: {column_types[orig_idx]}", Qt.ToolTipRole)
            
            # 为非数值列添加特殊样式，提示这些列可能不适合作为Y轴
            if column not in numeric_columns:
                self.secondary_y_axis_combo.setItemData(i+1, QColor("#FFA07A"), Qt.BackgroundRole)  # 浅珊瑚色背景
                self.secondary_y_axis_combo.setItemData(i+1, "注意: 此列不是数值类型，可能不适合作为Y轴", Qt.ToolTipRole)
        
        # 恢复信号连接
        self.x_axis_combo.blockSignals(False)
        self.y_axis_combo.blockSignals(False)
        self.secondary_y_axis_combo.blockSignals(False)
        
        # 尝试恢复之前的选择
        if x_current and x_current in columns:
            index = self.x_axis_combo.findText(x_current)
            if index >= 0:
                self.x_axis_combo.setCurrentIndex(index)
        elif len(columns) > 0:
            self.x_axis_combo.setCurrentIndex(0)  # 默认选第一列
            
        if y_current and y_current in sorted_columns:
            index = self.y_axis_combo.findText(y_current)
            if index >= 0:
                self.y_axis_combo.setCurrentIndex(index)
        elif len(numeric_columns) > 0:
            # 默认选择第一个数值列
            self.y_axis_combo.setCurrentIndex(0)
        elif len(sorted_columns) > 0:
            self.y_axis_combo.setCurrentIndex(0)  # 如果没有数值列，选第一列
            
        if secondary_y_current and secondary_y_current in sorted_columns:
            index = self.secondary_y_axis_combo.findText(secondary_y_current)
            if index >= 0:
                self.secondary_y_axis_combo.setCurrentIndex(index)
        else:
            self.secondary_y_axis_combo.setCurrentIndex(0)  # 默认选择"无"

    def _update_chart(self):
        """更新图表显示"""
        if not hasattr(self, '_has_matplotlib') or not self._has_matplotlib:
            self.chart_placeholder.setText("需要安装matplotlib才能使用图表功能")
            self.chart_placeholder.show()
            return
            
        # 检查是否有数据
        if self.model.rowCount() == 0 or self.model.columnCount() == 0:
            self.chart_placeholder.setText("没有数据可供图表显示")
            self.chart_placeholder.show()
            return
            
        # 获取选择的列
        if self.x_axis_combo.count() == 0 or self.y_axis_combo.count() == 0:
            self.chart_placeholder.setText("请选择要显示的列")
            self.chart_placeholder.show()
            return

        x_column = self.x_axis_combo.currentText()
        y_column = self.y_axis_combo.currentText()
        secondary_y_column = self.secondary_y_axis_combo.currentText()
        use_secondary_y = secondary_y_column != "无"
        
        chart_type = self.chart_type_combo.currentText()
        # chart_style = self.chart_style_combo.currentText()
        
        # 存储当前列信息，供点击事件使用
        self._current_chart_info = {
            'x_column': x_column,
            'y_column': y_column,
            'secondary_y_column': secondary_y_column if use_secondary_y else None,
            'chart_type': chart_type,
            'x_data': [],
            'y_data': [],
            'secondary_y_data': [],
            'row_indices': []  # 存储每个点对应的行索引，用于点击时查找原始数据
        }
        
        # 获取列索引
        x_index = -1
        y_index = -1
        secondary_y_index = -1
        
        for i in range(self.model.columnCount()):
            col_name = self.model.headerData(i, Qt.Horizontal)
            if col_name == x_column:
                x_index = i
            if col_name == y_column:
                y_index = i
            if use_secondary_y and col_name == secondary_y_column:
                secondary_y_index = i
                
        if x_index == -1 or y_index == -1:
            self.chart_placeholder.setText("无法找到选择的列")
            self.chart_placeholder.show()
            return
            
        if use_secondary_y and secondary_y_index == -1:
            self.chart_placeholder.setText("无法找到选择的附加Y轴列")
            self.chart_placeholder.show()
            return
        
        # 显示处理进度
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.chart_placeholder.setText("正在处理数据，请稍候...")
        self.chart_placeholder.show()
        QApplication.processEvents()  # 确保UI响应
        
        try:
            # 提取数据 - 优化部分
            row_count = self.model.rowCount()
            
            # 计算数据抽样率 - 如果数据量超过阈值，进行抽样
            MAX_POINTS = 1000  # 最大绘制点数
            sample_rate = 1
            sample_enabled = False
            
            if row_count > MAX_POINTS:
                sample_rate = max(1, int(row_count / MAX_POINTS))
                sample_enabled = True
                logger.info(f"图表数据量过大，启用1:{sample_rate}抽样")
            
            x_data = []
            y_data = []
            secondary_y_data = []
            all_data = []  # 用于箱线图和直方图
            row_indices = []  # 存储对应的行索引
            
            # 优化数据提取循环
            for row in range(0, row_count, sample_rate):
                x_val = self.model.data(self.model.index(row, x_index))
                y_val = self.model.data(self.model.index(row, y_index))
                
                # 附加Y轴数据
                if use_secondary_y:
                    secondary_y_val = self.model.data(self.model.index(row, secondary_y_index))
                
                # 尝试转换为数值
                try:
                    if y_val is not None:
                        y_val = float(y_val)
                        # X轴值可以是字符串或数字
                        if x_val is not None:
                            try:
                                x_val = float(x_val)
                                all_data.append(y_val)  # 对于箱线图和直方图，记录原始数值
                            except:
                                pass  # 保持字符串类型
                            x_data.append(x_val)
                            y_data.append(y_val)
                            row_indices.append(row)  # 记录行索引
                            
                            # 处理附加Y轴数据
                            if use_secondary_y and secondary_y_val is not None:
                                try:
                                    secondary_y_val = float(secondary_y_val)
                                    secondary_y_data.append(secondary_y_val)
                                except:
                                    # 如果无法转换为数值，使用NaN
                                    secondary_y_data.append(float('nan'))
                except:
                    pass  # 忽略无法转换的值
            
            # 检查是否有足够的数据
            if len(x_data) < 1 or len(y_data) < 1:
                self.chart_placeholder.setText("没有足够的数值数据用于绘图")
                self.chart_placeholder.show()
                QApplication.restoreOverrideCursor()
                return
                
            if use_secondary_y and len(secondary_y_data) < 1:
                self.chart_placeholder.setText("附加Y轴没有足够的数值数据")
                self.chart_placeholder.show()
                QApplication.restoreOverrideCursor()
                return
                
            # 存储数据用于点击事件
            self._current_chart_info['x_data'] = x_data
            self._current_chart_info['y_data'] = y_data
            self._current_chart_info['secondary_y_data'] = secondary_y_data if use_secondary_y else []
            self._current_chart_info['row_indices'] = row_indices
            self._current_chart_info['sample_rate'] = sample_rate
            self._current_chart_info['sample_enabled'] = sample_enabled
            
                
            # 清除图表
            self.ax.clear()
            
            # 获取用户自定义标题，如果没有则使用默认标题
            chart_title = ""
            if not chart_title:
                if use_secondary_y:
                    chart_title = f"{y_column} 和 {secondary_y_column} vs {x_column}"
                else:
                    chart_title = f"{y_column} vs {x_column}"
            
            # 如果启用了抽样，在标题中显示提示
            if sample_enabled:
                chart_title += f" (已抽样 1:{sample_rate})"
            
            # 设置双Y轴
            if use_secondary_y and chart_type not in ["饼图", "箱线图", "直方图", "热力图"]:
                # 创建第二个Y轴
                ax2 = self.ax.twinx()
            
            # 绘制新图表 - 优化绘图选项
            try:
                # 根据数据量调整绘图参数
                marker_size = 50 if len(x_data) < 100 else (20 if len(x_data) < 500 else 5)
                line_width = 2 if len(x_data) < 500 else 1
                alpha = 0.7 if len(x_data) < 500 else 0.5
                
                # 散点图大小优化
                scatter_size = 50 if len(x_data) < 100 else (20 if len(x_data) < 500 else 5)
                
                # 减少热力图注释，提高性能
                heat_annot = len(x_data) < 100
                
                if chart_type == "柱状图":
                    if use_secondary_y:
                        # 绘制主Y轴
                        bar_width = 0.35
                        x_pos = range(len(x_data))
                        bars1 = self.ax.bar([p - bar_width/2 for p in x_pos], y_data, bar_width, color='royalblue', alpha=alpha, label=y_column, picker=5)
                        
                        # 绘制附加Y轴
                        bars2 = ax2.bar([p + bar_width/2 for p in x_pos], secondary_y_data, bar_width, color='green', alpha=alpha, label=secondary_y_column, picker=5)
                        
                        # 设置刻度标签
                        self.ax.set_xticks(x_pos)
                        
                        # 优化X轴标签 - 数据量大时减少标签显示
                        if len(x_data) > 20:
                            step = max(1, len(x_data) // 20)
                            self.ax.set_xticks(x_pos[::step])
                            self.ax.set_xticklabels(x_data[::step], rotation=45, ha='right')
                        else:
                            self.ax.set_xticklabels(x_data, rotation=45, ha='right')
                    else:
                        bars = self.ax.bar(x_data, y_data, color='royalblue', alpha=alpha, picker=5)
                        
                        # 优化X轴标签 - 数据量大时减少标签显示
                        if len(x_data) > 20:
                            step = max(1, len(x_data) // 20)
                            self.ax.set_xticks(x_data[::step])
                            self.ax.set_xticklabels(x_data[::step], rotation=45, ha='right')
                elif chart_type == "折线图":
                    # 折线图优化 - 数据量大时减少标记点
                    use_markers = len(x_data) < 100
                    
                    if use_secondary_y:
                        # 绘制主Y轴
                        if use_markers:
                            line1, = self.ax.plot(x_data, y_data, marker='o', markersize=marker_size/5, color='royalblue', linewidth=line_width, label=y_column, picker=5)
                        else:
                            line1, = self.ax.plot(x_data, y_data, color='royalblue', linewidth=line_width, label=y_column, picker=5)
                        
                        # 绘制附加Y轴
                        if use_markers:
                            line2, = ax2.plot(x_data, secondary_y_data, marker='s', markersize=marker_size/5, color='green', linewidth=line_width, label=secondary_y_column, picker=5)
                        else:
                            line2, = ax2.plot(x_data, secondary_y_data, color='green', linewidth=line_width, label=secondary_y_column, picker=5)
                        
                        # 设置图例
                        if self.show_legend_check.isChecked():
                            from matplotlib.legend import Legend
                            leg = Legend(self.ax, [line1, line2], [y_column, secondary_y_column], loc='upper left')
                            self.ax.add_artist(leg)
                    else:
                        if use_markers:
                            self.ax.plot(x_data, y_data, marker='o', markersize=marker_size/5, color='green', linewidth=line_width, picker=5)
                        else:
                            self.ax.plot(x_data, y_data, color='green', linewidth=line_width, picker=5)
                elif chart_type == "饼图":
                    # 饼图特殊处理，只使用正值
                    # 饼图对大数据不友好，限制最大扇区数
                    MAX_WEDGES = 15
                    if len(x_data) > MAX_WEDGES:
                        # 取前MAX_WEDGES-1个值和"其他"
                        x_data_limited = x_data[:MAX_WEDGES-1]
                        y_data_limited = [max(0, y) for y in y_data[:MAX_WEDGES-1]]
                        other_sum = sum([max(0, y) for y in y_data[MAX_WEDGES-1:]])
                        
                        if other_sum > 0:
                            x_data_limited.append("其他")
                            y_data_limited.append(other_sum)
                            
                        if sum(y_data_limited) > 0:  # 确保有正值
                            wedges, texts, autotexts = self.ax.pie(y_data_limited, labels=x_data_limited, autopct='%1.1f%%', 
                                            shadow=True, startangle=90,
                                            colors=['#ff9999','#66b3ff','#99ff99','#ffcc99','#c2c2f0','#ffb3e6'],
                                            wedgeprops=dict(picker=True))
                            self.ax.axis('equal')  # 使饼图为正圆
                        else:
                            self.ax.text(0.5, 0.5, "没有正值用于饼图", 
                                        horizontalalignment='center', verticalalignment='center')
                    else:
                        labels = x_data
                        sizes = [max(0, y) for y in y_data]
                        if sum(sizes) > 0:  # 确保有正值
                            wedges, texts, autotexts = self.ax.pie(sizes, labels=labels, autopct='%1.1f%%', 
                                            shadow=True, startangle=90,
                                            colors=['#ff9999','#66b3ff','#99ff99','#ffcc99','#c2c2f0','#ffb3e6'],
                                            wedgeprops=dict(picker=True))
                            self.ax.axis('equal')  # 使饼图为正圆
                        else:
                            self.ax.text(0.5, 0.5, "没有正值用于饼图", 
                                        horizontalalignment='center', verticalalignment='center')
                elif chart_type == "散点图":
                    if use_secondary_y:
                        # 绘制主Y轴
                        scatter1 = self.ax.scatter(x_data, y_data, alpha=alpha, s=scatter_size, c='blue', edgecolors='gray', label=y_column, picker=5)
                        
                        # 绘制附加Y轴
                        scatter2 = ax2.scatter(x_data, secondary_y_data, alpha=alpha, s=scatter_size, c='green', edgecolors='gray', label=secondary_y_column, picker=5)
                        
                        # 设置图例
                        if self.show_legend_check.isChecked():
                            from matplotlib.legend import Legend
                            leg = Legend(self.ax, [scatter1, scatter2], [y_column, secondary_y_column], loc='upper left')
                            self.ax.add_artist(leg)
                    else:
                        self.ax.scatter(x_data, y_data, alpha=alpha, s=scatter_size, c='blue', edgecolors='gray', picker=5)
                elif chart_type == "箱线图":
                    # 箱线图需要按类别分组数据 - 对大数据做优化
                    if len(all_data) > 0:
                        # 如果数据量过大，随机抽样
                        if len(all_data) > 1000:
                            import random
                            all_data = random.sample(all_data, 1000)
                            
                        self.ax.boxplot(all_data)
                        self.ax.set_title(f"{y_column} 的分布")
                        self.ax.set_ylabel(y_column)
                        self.ax.set_xlabel("数据分布")
                    else:
                        self.ax.text(0.5, 0.5, "没有足够的数据用于箱线图", 
                                    horizontalalignment='center', verticalalignment='center')
                elif chart_type == "直方图":
                    # 直方图只需要Y值 - 对大数据做优化
                    if len(all_data) > 0:
                        # 对于大数据，减少bin数量提高性能
                        if len(all_data) > 10000:
                            num_bins = 20
                        else:
                            # 自动确定bin数量
                            num_bins = min(20, max(5, int(len(all_data) / 10)))
                            
                        self.ax.hist(all_data, bins=num_bins, alpha=alpha, color='royalblue', edgecolor='black')
                        self.ax.set_title(f"{y_column} 的分布")
                        self.ax.set_ylabel("频率")
                        self.ax.set_xlabel(y_column)
                    else:
                        self.ax.text(0.5, 0.5, "没有足够的数据用于直方图", 
                                    horizontalalignment='center', verticalalignment='center')
                elif chart_type == "热力图":
                    # 热力图需要导入seaborn - 对大数据做优化
                    try:
                        import numpy as np
                        import seaborn as sns
                        
                        # 对于大数据，进行聚合或抽样
                        MAX_HEATMAP_SIZE = 50  # 最大热力图大小
                        
                        # 将数据转换为矩阵
                        unique_x = np.unique(x_data)
                        unique_y = np.unique(y_data)
                        
                        # 如果唯一值太多，进行聚合
                        if len(unique_x) > MAX_HEATMAP_SIZE or len(unique_y) > MAX_HEATMAP_SIZE:
                            self.chart_placeholder.setText(f"数据点过多，热力图性能可能降低。X轴：{len(unique_x)}个唯一值，Y轴：{len(unique_y)}个唯一值")
                            self.chart_placeholder.show()
                            
                            # 对X和Y轴进行分箱处理
                            x_bins = min(MAX_HEATMAP_SIZE, len(unique_x))
                            y_bins = min(MAX_HEATMAP_SIZE, len(unique_y))
                            
                            # 计算分箱边界
                            if isinstance(unique_x[0], (int, float)):
                                x_edges = np.linspace(min(unique_x), max(unique_x), x_bins+1)
                            else:
                                x_edges = np.arange(x_bins+1)
                                
                            if isinstance(unique_y[0], (int, float)):
                                y_edges = np.linspace(min(unique_y), max(unique_y), y_bins+1)
                            else:
                                y_edges = np.arange(y_bins+1)
                            
                            # 创建2D直方图
                            if isinstance(unique_x[0], (int, float)) and isinstance(unique_y[0], (int, float)):
                                hist, x_edges, y_edges = np.histogram2d(x_data, y_data, bins=[x_edges, y_edges])
                                # 转置以匹配热力图的期望方向
                                hist = hist.T
                                
                                # 创建刻度标签
                                x_labels = [f"{x_edges[i]:.1f}-{x_edges[i+1]:.1f}" for i in range(len(x_edges)-1)]
                                y_labels = [f"{y_edges[i]:.1f}-{y_edges[i+1]:.1f}" for i in range(len(y_edges)-1)]
                                
                                # 绘制热力图
                                sns.heatmap(hist, annot=heat_annot, fmt="g", cmap="viridis", 
                                           xticklabels=x_labels, yticklabels=y_labels, ax=self.ax)
                                self.ax.set_title(f"{y_column} vs {x_column} 热力图 (已聚合)")
                            else:
                                # 如果不是数值型，则限制显示的唯一值数量
                                if len(unique_x) > MAX_HEATMAP_SIZE:
                                    x_data = [str(x) for x in x_data]
                                    most_common_x = {}
                                    for x in x_data:
                                        most_common_x[x] = most_common_x.get(x, 0) + 1
                                    unique_x = [x for x, _ in sorted(most_common_x.items(), key=lambda item: item[1], reverse=True)[:MAX_HEATMAP_SIZE-1]]
                                    unique_x.append("其他")
                                
                                if len(unique_y) > MAX_HEATMAP_SIZE:
                                    y_data = [str(y) for y in y_data]
                                    most_common_y = {}
                                    for y in y_data:
                                        most_common_y[y] = most_common_y.get(y, 0) + 1
                                    unique_y = [y for y, _ in sorted(most_common_y.items(), key=lambda item: item[1], reverse=True)[:MAX_HEATMAP_SIZE-1]]
                                    unique_y.append("其他")
                                
                                # 创建矩阵
                                matrix = np.zeros((len(unique_y), len(unique_x)))
                                
                                # 填充矩阵
                                for i, x in enumerate(x_data):
                                    if i < len(y_data):
                                        x_str = str(x)
                                        y_str = str(y_data[i])
                                        
                                        x_idx = unique_x.index(x_str) if x_str in unique_x else len(unique_x) - 1
                                        y_idx = unique_y.index(y_str) if y_str in unique_y else len(unique_y) - 1
                                        
                                        matrix[y_idx, x_idx] += 1
                                
                                # 绘制热力图
                                sns.heatmap(matrix, annot=heat_annot, fmt="g", cmap="viridis", 
                                           xticklabels=unique_x, yticklabels=unique_y, ax=self.ax)
                                self.ax.set_title(f"{y_column} vs {x_column} 热力图 (Top {MAX_HEATMAP_SIZE})")
                        elif len(unique_x) > 1 and len(unique_y) > 1:
                            matrix = np.zeros((len(unique_y), len(unique_x)))
                            
                            # 填充矩阵
                            for i, x in enumerate(x_data):
                                if i < len(y_data):
                                    x_idx = np.where(unique_x == x)[0]
                                    y_idx = np.where(unique_y == y_data[i])[0]
                                    if len(x_idx) > 0 and len(y_idx) > 0:
                                        matrix[y_idx[0], x_idx[0]] += 1
                            
                            # 绘制热力图
                            sns.heatmap(matrix, annot=heat_annot, fmt="g", cmap="viridis", 
                                       xticklabels=unique_x, yticklabels=unique_y, ax=self.ax)
                            self.ax.set_title(f"{y_column} vs {x_column} 热力图")
                        else:
                            self.ax.text(0.5, 0.5, "数据点不足以创建热力图", 
                                        horizontalalignment='center', verticalalignment='center')
                    except ImportError:
                        self.ax.text(0.5, 0.5, "需要安装seaborn库才能创建热力图", 
                                    horizontalalignment='center', verticalalignment='center')
                elif chart_type == "面积图":
                    if use_secondary_y:
                        # 绘制主Y轴
                        self.ax.fill_between(x_data, y_data, alpha=alpha, color='royalblue', label=y_column)
                        self.ax.plot(x_data, y_data, color='navy', linewidth=line_width)
                        
                        # 绘制附加Y轴
                        ax2.fill_between(x_data, secondary_y_data, alpha=alpha-0.1, color='green', label=secondary_y_column)
                        ax2.plot(x_data, secondary_y_data, color='darkgreen', linewidth=line_width)
                        
                        # 设置图例
                        if self.show_legend_check.isChecked():
                            self.ax.legend(loc='upper left')
                            ax2.legend(loc='upper right')
                    else:
                        self.ax.fill_between(x_data, y_data, alpha=alpha, color='royalblue')
                        self.ax.plot(x_data, y_data, color='navy', linewidth=line_width)
                
                # 设置标题和标签
                self.ax.set_title(chart_title, fontsize=14, fontweight='bold')
                
                # 设置网格
                if self.show_grid_check.isChecked() and chart_type != "饼图":
                    self.ax.grid(True, linestyle='--', alpha=0.7)
                else:
                    self.ax.grid(False)
                    
                # 设置图例
                if self.show_legend_check.isChecked():
                    if chart_type not in ["饼图", "箱线图", "直方图", "热力图"] and not use_secondary_y:
                        self.ax.legend([y_column])
                        
                # 标签设置
                if chart_type != "饼图":
                    self.ax.set_xlabel(x_column, fontsize=12)
                    self.ax.set_ylabel(y_column, fontsize=12)
                    
                    # 设置附加Y轴的标签
                    if use_secondary_y and chart_type not in ["箱线图", "直方图", "热力图"]:
                        ax2.set_ylabel(secondary_y_column, fontsize=12, color='green')
                        ax2.tick_params(axis='y', colors='green')

                    
                # 刷新图表
                self.canvas.draw()
                
                # 确保图表可见
                self.chart_placeholder.hide()
                
            except Exception as e:
                logger.error(f"绘制图表时出错: {str(e)}")
                self.chart_placeholder.setText(f"绘制图表失败: {str(e)}")
                self.chart_placeholder.show()
        
        finally:
            # 恢复鼠标指针
            QApplication.restoreOverrideCursor()
    
    def _copy_sql(self):
        """复制SQL查询到剪贴板"""
        sql_text = self.sql_text.toPlainText()
        if sql_text:
            clipboard = QGuiApplication.clipboard()
            clipboard.setText(sql_text)
            QMessageBox.information(self, "复制成功", "SQL查询已复制到剪贴板")
    
    def _update_query_info(self, query_sql, execution_time=None):
        """更新查询信息标签页
        
        Args:
            query_sql: 执行的SQL查询
            execution_time: 执行时间（秒）
        """
        # 清空当前信息
        self.info_model.setRowCount(0)
        
        # 设置SQL文本
        if query_sql:
            self.sql_text.setText(query_sql)
        else:
            self.sql_text.setText("无可用的SQL查询")
            
        # 设置执行时间
        if execution_time is not None:
            self.execution_time_label.setText(f"执行时间: {execution_time:.4f} 秒")
        else:
            self.execution_time_label.setText("执行时间: 未知")
        
        # 获取结果集信息
        row_count = self.model.rowCount()
        col_count = self.model.columnCount()
        
        # 更新摘要标签
        self.row_count_label.setText(f"结果行数: {row_count}")
        self.column_count_label.setText(f"结果列数: {col_count}")
        self.query_status_label.setText("状态: 成功" if row_count >= 0 else "状态: 失败")
            
        # 添加结果集信息
        info_items = [
            ("执行状态", "成功" if row_count >= 0 else "失败"),
            ("结果行数", str(row_count)),
            ("结果列数", str(col_count)),
            ("过滤状态", "已过滤" if self.model.is_filtered() else "未过滤")
        ]
        
        # 添加执行时间
        if execution_time is not None:
            info_items.append(("执行时间", f"{execution_time:.4f} 秒"))
            
            # 如果执行时间很长，添加性能建议
            if execution_time > 1.0:
                info_items.append(("性能建议", "考虑添加WHERE条件或LIMIT子句优化查询"))
        
        # 添加查询类型信息
        if query_sql:
            query_upper = query_sql.strip().upper()
            if query_upper.startswith("SELECT"):
                info_items.append(("查询类型", "SELECT (查询)"))
                
                # 检查是否有常见的优化问题
                if "WHERE" not in query_upper:
                    info_items.append(("优化建议", "添加WHERE条件可能会提高查询性能"))
                    
                if "LIMIT" not in query_upper:
                    info_items.append(("LIMIT状态", "系统自动添加LIMIT 1000"))
                else:
                    info_items.append(("LIMIT状态", "用户已指定LIMIT"))
                    
            elif query_upper.startswith("INSERT"):
                info_items.append(("查询类型", "INSERT (插入)"))
            elif query_upper.startswith("UPDATE"):
                info_items.append(("查询类型", "UPDATE (更新)"))
            elif query_upper.startswith("DELETE"):
                info_items.append(("查询类型", "DELETE (删除)"))
            elif query_upper.startswith("CREATE"):
                info_items.append(("查询类型", "CREATE (创建)"))
            elif query_upper.startswith("DROP"):
                info_items.append(("查询类型", "DROP (删除对象)"))
            elif query_upper.startswith("ALTER"):
                info_items.append(("查询类型", "ALTER (修改)"))
        
        # 添加列信息
        info_items.append(("", ""))  # 空行分隔符
        info_items.append(("列信息", ""))  # 小标题
        
        for i in range(min(col_count, 10)):  # 限制最多显示10列信息
            col_name = self.model.headerData(i, Qt.Horizontal)
            col_type = "未知"
            if i < len(self.model._column_types):
                col_type = self.model._column_types[i]
            info_items.append((f"列 {i+1}", f"{col_name} ({col_type})"))
            
        # 如果有更多列没有显示
        if col_count > 10:
            info_items.append(("其他列", f"还有 {col_count - 10} 列未显示"))
            
        # 添加到表格
        for row, (name, value) in enumerate(info_items):
            name_item = QStandardItem(name)
            value_item = QStandardItem(value)
            
            # 为小标题设置样式
            if name and not value:
                font = QFont()
                font.setBold(True)
                name_item.setFont(font)
                # 灰色背景
                name_item.setBackground(QColor("#f0f0f0"))
                value_item.setBackground(QColor("#f0f0f0"))
            
            # 为空行设置较小高度
            elif not name and not value:
                name_item.setData(15, Qt.SizeHintRole)  # 设置较小的高度
            
            self.info_model.appendRow([name_item, value_item])
            
        # 调整列宽
        self.info_table.resizeColumnsToContents()
        self.info_table.horizontalHeader().setStretchLastSection(True)

    def _export_chart(self):
        """导出图表"""
        if not hasattr(self, '_has_matplotlib') or not self._has_matplotlib:
            self.chart_placeholder.setText("需要安装matplotlib才能使用图表功能")
            self.chart_placeholder.show()
            QMessageBox.warning(self, "导出失败", "需要安装matplotlib才能使用图表功能")
            return
            
        # 检查是否有图表
        if self.model.rowCount() == 0 or self.model.columnCount() == 0:
            QMessageBox.warning(self, "导出失败", "没有数据可供导出")
            return
            
        # 弹出文件保存对话框
        file_dialog = QFileDialog(self)
        file_dialog.setWindowTitle("保存图表")
        file_dialog.setAcceptMode(QFileDialog.AcceptSave)
        file_dialog.setNameFilter("PNG图像 (*.png);;SVG图像 (*.svg);;PDF文档 (*.pdf);;JPG图像 (*.jpg);;所有文件 (*)")
        file_dialog.setDefaultSuffix("png")
        
        if file_dialog.exec_() == QFileDialog.Accepted:
            file_path = file_dialog.selectedFiles()[0]
            
            # 显示进度指示器
            QApplication.setOverrideCursor(Qt.WaitCursor)
            
            try:
                # 是否使用高分辨率
                dpi_dialog = QDialog(self)
                dpi_dialog.setWindowTitle("选择导出质量")
                dpi_layout = QVBoxLayout(dpi_dialog)
                
                # 提示标签
                label = QLabel("请选择导出图表的质量 (分辨率)")
                dpi_layout.addWidget(label)
                
                # DPI选择
                dpi_combo = QComboBox()
                dpi_combo.addItem("标准质量 (100 DPI)", 100)
                dpi_combo.addItem("高质量 (200 DPI)", 200)
                dpi_combo.addItem("印刷质量 (300 DPI)", 300)
                dpi_combo.addItem("超高质量 (600 DPI)", 600)
                
                # 大数据警告
                if self.model.rowCount() > 5000:
                    warning_label = QLabel("注意: 数据量较大，选择高分辨率可能会导致导出过程较慢")
                    warning_label.setObjectName("warning_label")
                    dpi_layout.addWidget(warning_label)
                
                dpi_layout.addWidget(dpi_combo)
                
                # 按钮
                buttons = QHBoxLayout()
                ok_button = QPushButton("确定")
                cancel_button = QPushButton("取消")
                ok_button.clicked.connect(dpi_dialog.accept)
                cancel_button.clicked.connect(dpi_dialog.reject)
                buttons.addWidget(ok_button)
                buttons.addWidget(cancel_button)
                dpi_layout.addLayout(buttons)
                
                # 执行对话框
                if dpi_dialog.exec_() != QDialog.Accepted:
                    QApplication.restoreOverrideCursor()
                    return
                
                # 为了更好的导出质量，创建一个高分辨率的图像
                dpi = dpi_combo.currentData()  # 从选择框获取DPI值
                
                # 获取当前图表类型和配置
                if not hasattr(self, '_current_chart_info'):
                    QMessageBox.warning(self, "导出失败", "无法获取当前图表信息")
                    QApplication.restoreOverrideCursor()
                    return
                    
                chart_info = self._current_chart_info
                chart_type = chart_info['chart_type']
                
                # 获取用户自定义标题
                chart_title = ""
                if not chart_title:
                    if chart_info['secondary_y_column']:
                        chart_title = f"{chart_info['y_column']} 和 {chart_info['secondary_y_column']} vs {chart_info['x_column']}"
                    else:
                        chart_title = f"{chart_info['y_column']} vs {chart_info['x_column']}"
                
                # 如果启用了抽样，在标题中显示提示
                if chart_info.get('sample_enabled', False):
                    chart_title += f" (已抽样 1:{chart_info.get('sample_rate', 1)})"
                
                # 创建一个新的图表用于导出
                from matplotlib.figure import Figure
                from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
                
                # 确定图表尺寸和dpi - 使用合适的尺寸
                figsize = (10, 8)  # 合理的默认尺寸
                fig = Figure(figsize=figsize, dpi=dpi)
                canvas = FigureCanvas(fig)
                
                # 创建新的子图
                ax = fig.add_subplot(111)
                
                # 根据图表类型绘制图表 - 使用与_update_chart相同的优化逻辑
                x_data = chart_info['x_data']
                y_data = chart_info['y_data']
                secondary_y_data = chart_info.get('secondary_y_data', [])
                use_secondary_y = chart_info['secondary_y_column'] is not None and len(secondary_y_data) > 0
                
                # 根据数据量调整绘图参数
                marker_size = 50 if len(x_data) < 100 else (20 if len(x_data) < 500 else 5)
                line_width = 2 if len(x_data) < 500 else 1
                alpha = 0.7 if len(x_data) < 500 else 0.5
                
                # 散点图大小优化
                scatter_size = 50 if len(x_data) < 100 else (20 if len(x_data) < 500 else 5)
                
                # 绘制图表 - 针对每种图表类型进行优化
                if chart_type == "柱状图":
                    if use_secondary_y:
                        # 设置双Y轴
                        ax2 = ax.twinx()
                        
                        # 绘制主Y轴
                        bar_width = 0.35
                        x_pos = range(len(x_data))
                        ax.bar([p - bar_width/2 for p in x_pos], y_data, bar_width, color='royalblue', 
                              alpha=alpha, label=chart_info['y_column'])
                        
                        # 绘制附加Y轴
                        ax2.bar([p + bar_width/2 for p in x_pos], secondary_y_data, bar_width, 
                               color='green', alpha=alpha, label=chart_info['secondary_y_column'])
                        
                        # 设置刻度标签
                        ax.set_xticks(x_pos)
                        
                        # 优化X轴标签 - 数据量大时减少标签显示
                        if len(x_data) > 20:
                            step = max(1, len(x_data) // 20)
                            ax.set_xticks(x_pos[::step])
                            ax.set_xticklabels(x_data[::step], rotation=45, ha='right')
                        else:
                            ax.set_xticklabels(x_data, rotation=45, ha='right')
                            
                        # 设置Y轴标签
                        ax.set_ylabel(chart_info['y_column'], fontsize=12)
                        ax2.set_ylabel(chart_info['secondary_y_column'], fontsize=12, color='green')
                        ax2.tick_params(axis='y', colors='green')
                        
                        # 设置图例
                        if self.show_legend_check.isChecked():
                            ax.legend(loc='upper left')
                            ax2.legend(loc='upper right')
                    else:
                        ax.bar(x_data, y_data, color='royalblue', alpha=alpha)
                        
                        # 优化X轴标签
                        if len(x_data) > 20:
                            step = max(1, len(x_data) // 20)
                            ax.set_xticks(x_data[::step])
                            ax.set_xticklabels(x_data[::step], rotation=45, ha='right')
                elif chart_type == "折线图":
                    # 折线图优化 - 数据量大时减少标记点
                    use_markers = len(x_data) < 100
                    
                    if use_secondary_y:
                        # 设置双Y轴
                        ax2 = ax.twinx()
                        
                        # 绘制主Y轴
                        if use_markers:
                            line1, = ax.plot(x_data, y_data, marker='o', markersize=marker_size/5, 
                                           color='royalblue', linewidth=line_width, label=chart_info['y_column'])
                        else:
                            line1, = ax.plot(x_data, y_data, color='royalblue', linewidth=line_width, 
                                           label=chart_info['y_column'])
                        
                        # 绘制附加Y轴
                        if use_markers:
                            line2, = ax2.plot(x_data, secondary_y_data, marker='s', markersize=marker_size/5, 
                                            color='green', linewidth=line_width, label=chart_info['secondary_y_column'])
                        else:
                            line2, = ax2.plot(x_data, secondary_y_data, color='green', linewidth=line_width, 
                                            label=chart_info['secondary_y_column'])
                        
                        # 设置Y轴标签
                        ax.set_ylabel(chart_info['y_column'], fontsize=12)
                        ax2.set_ylabel(chart_info['secondary_y_column'], fontsize=12, color='green')
                        ax2.tick_params(axis='y', colors='green')
                        
                        # 设置图例
                        if self.show_legend_check.isChecked():
                            ax.legend(loc='upper left')
                            ax2.legend(loc='upper right')
                    else:
                        if use_markers:
                            ax.plot(x_data, y_data, marker='o', markersize=marker_size/5, 
                                   color='green', linewidth=line_width)
                        else:
                            ax.plot(x_data, y_data, color='green', linewidth=line_width)
                elif chart_type == "饼图":
                    # 饼图特殊处理，只使用正值
                    # 饼图对大数据不友好，限制最大扇区数
                    MAX_WEDGES = 15
                    if len(x_data) > MAX_WEDGES:
                        # 取前MAX_WEDGES-1个值和"其他"
                        x_data_limited = x_data[:MAX_WEDGES-1]
                        y_data_limited = [max(0, y) for y in y_data[:MAX_WEDGES-1]]
                        other_sum = sum([max(0, y) for y in y_data[MAX_WEDGES-1:]])
                        
                        if other_sum > 0:
                            x_data_limited.append("其他")
                            y_data_limited.append(other_sum)
                            
                        if sum(y_data_limited) > 0:  # 确保有正值
                            ax.pie(y_data_limited, labels=x_data_limited, autopct='%1.1f%%', 
                                  shadow=True, startangle=90,
                                  colors=['#ff9999','#66b3ff','#99ff99','#ffcc99','#c2c2f0','#ffb3e6'])
                            ax.axis('equal')  # 使饼图为正圆
                        else:
                            ax.text(0.5, 0.5, "没有正值用于饼图", 
                                   horizontalalignment='center', verticalalignment='center')
                    else:
                        labels = x_data
                        sizes = [max(0, y) for y in y_data]
                        if sum(sizes) > 0:  # 确保有正值
                            ax.pie(sizes, labels=labels, autopct='%1.1f%%', 
                                  shadow=True, startangle=90,
                                  colors=['#ff9999','#66b3ff','#99ff99','#ffcc99','#c2c2f0','#ffb3e6'])
                            ax.axis('equal')  # 使饼图为正圆
                        else:
                            ax.text(0.5, 0.5, "没有正值用于饼图", 
                                   horizontalalignment='center', verticalalignment='center')
                elif chart_type == "散点图":
                    if use_secondary_y:
                        # 设置双Y轴
                        ax2 = ax.twinx()
                        
                        # 绘制主Y轴
                        ax.scatter(x_data, y_data, alpha=alpha, s=scatter_size, c='blue', 
                                  edgecolors='gray', label=chart_info['y_column'])
                        
                        # 绘制附加Y轴
                        ax2.scatter(x_data, secondary_y_data, alpha=alpha, s=scatter_size, 
                                   c='green', edgecolors='gray', label=chart_info['secondary_y_column'])
                        
                        # 设置Y轴标签
                        ax.set_ylabel(chart_info['y_column'], fontsize=12)
                        ax2.set_ylabel(chart_info['secondary_y_column'], fontsize=12, color='green')
                        ax2.tick_params(axis='y', colors='green')
                        
                        # 设置图例
                        if self.show_legend_check.isChecked():
                            ax.legend(loc='upper left')
                            ax2.legend(loc='upper right')
                    else:
                        ax.scatter(x_data, y_data, alpha=alpha, s=scatter_size, c='blue', edgecolors='gray')
                elif chart_type == "箱线图":
                    # 从chart_info中获取all_data
                    import random
                    all_data = y_data  # 使用y轴数据作为箱线图数据
                    
                    # 如果数据量过大，随机抽样
                    if len(all_data) > 1000:
                        all_data = random.sample(all_data, 1000)
                        
                    ax.boxplot(all_data)
                    ax.set_xlabel("数据分布", fontsize=12)
                    ax.set_ylabel(chart_info['y_column'], fontsize=12)
                elif chart_type == "直方图":
                    # 使用y轴数据作为直方图数据
                    all_data = y_data
                    
                    # 对于大数据，减少bin数量提高性能
                    if len(all_data) > 10000:
                        num_bins = 20
                    else:
                        # 自动确定bin数量
                        num_bins = min(20, max(5, int(len(all_data) / 10)))
                        
                    ax.hist(all_data, bins=num_bins, alpha=alpha, color='royalblue', edgecolor='black')
                    ax.set_xlabel(chart_info['y_column'], fontsize=12)
                    ax.set_ylabel("频率", fontsize=12)
                elif chart_type == "热力图":
                    # 热力图需要导入seaborn
                    try:
                        import numpy as np
                        import seaborn as sns
                        
                        # 对于大数据，进行聚合或抽样
                        MAX_HEATMAP_SIZE = 50  # 最大热力图大小
                        
                        # 将数据转换为矩阵
                        unique_x = np.unique(x_data)
                        unique_y = np.unique(y_data)
                        
                        # 如果唯一值太多，进行聚合
                        if len(unique_x) > MAX_HEATMAP_SIZE or len(unique_y) > MAX_HEATMAP_SIZE:
                            # 对X和Y轴进行分箱处理
                            x_bins = min(MAX_HEATMAP_SIZE, len(unique_x))
                            y_bins = min(MAX_HEATMAP_SIZE, len(unique_y))
                            
                            # 计算分箱边界
                            if isinstance(unique_x[0], (int, float)):
                                x_edges = np.linspace(min(unique_x), max(unique_x), x_bins+1)
                            else:
                                x_edges = np.arange(x_bins+1)
                                
                            if isinstance(unique_y[0], (int, float)):
                                y_edges = np.linspace(min(unique_y), max(unique_y), y_bins+1)
                            else:
                                y_edges = np.arange(y_bins+1)
                            
                            # 创建2D直方图
                            if isinstance(unique_x[0], (int, float)) and isinstance(unique_y[0], (int, float)):
                                hist, x_edges, y_edges = np.histogram2d(x_data, y_data, bins=[x_edges, y_edges])
                                # 转置以匹配热力图的期望方向
                                hist = hist.T
                                
                                # 创建刻度标签
                                x_labels = [f"{x_edges[i]:.1f}-{x_edges[i+1]:.1f}" for i in range(len(x_edges)-1)]
                                y_labels = [f"{y_edges[i]:.1f}-{y_edges[i+1]:.1f}" for i in range(len(y_edges)-1)]
                                
                                # 绘制热力图
                                sns.heatmap(hist, annot=heat_annot, fmt="g", cmap="viridis", 
                                           xticklabels=x_labels, yticklabels=y_labels, ax=ax)
                                self.ax.set_title(f"{chart_info['y_column']} vs {chart_info['x_column']} 热力图 (已聚合)")
                            else:
                                # 如果不是数值型，则限制显示的唯一值数量
                                if len(unique_x) > MAX_HEATMAP_SIZE:
                                    x_data = [str(x) for x in x_data]
                                    most_common_x = {}
                                    for x in x_data:
                                        most_common_x[x] = most_common_x.get(x, 0) + 1
                                    unique_x = [x for x, _ in sorted(most_common_x.items(), key=lambda item: item[1], reverse=True)[:MAX_HEATMAP_SIZE-1]]
                                    unique_x.append("其他")
                                
                                if len(unique_y) > MAX_HEATMAP_SIZE:
                                    y_data = [str(y) for y in y_data]
                                    most_common_y = {}
                                    for y in y_data:
                                        most_common_y[y] = most_common_y.get(y, 0) + 1
                                    unique_y = [y for y, _ in sorted(most_common_y.items(), key=lambda item: item[1], reverse=True)[:MAX_HEATMAP_SIZE-1]]
                                    unique_y.append("其他")
                                
                                # 创建矩阵
                                matrix = np.zeros((len(unique_y), len(unique_x)))
                                
                                # 填充矩阵
                                for i, x in enumerate(x_data):
                                    if i < len(y_data):
                                        x_str = str(x)
                                        y_str = str(y_data[i])
                                        
                                        x_idx = unique_x.index(x_str) if x_str in unique_x else len(unique_x) - 1
                                        y_idx = unique_y.index(y_str) if y_str in unique_y else len(unique_y) - 1
                                        
                                        matrix[y_idx, x_idx] += 1
                                
                                # 绘制热力图
                                sns.heatmap(matrix, annot=heat_annot, fmt="g", cmap="viridis", 
                                           xticklabels=unique_x, yticklabels=unique_y, ax=ax)
                                self.ax.set_title(f"{chart_info['y_column']} vs {chart_info['x_column']} 热力图 (Top {MAX_HEATMAP_SIZE})")
                        elif len(unique_x) > 1 and len(unique_y) > 1:
                            matrix = np.zeros((len(unique_y), len(unique_x)))
                            
                            # 填充矩阵
                            for i, x in enumerate(x_data):
                                if i < len(y_data):
                                    x_idx = np.where(unique_x == x)[0]
                                    y_idx = np.where(unique_y == y_data[i])[0]
                                    if len(x_idx) > 0 and len(y_idx) > 0:
                                        matrix[y_idx[0], x_idx[0]] += 1
                            
                            # 绘制热力图
                            sns.heatmap(matrix, annot=heat_annot, fmt="g", cmap="viridis", 
                                       xticklabels=unique_x, yticklabels=unique_y, ax=ax)
                            self.ax.set_title(f"{chart_info['y_column']} vs {chart_info['x_column']} 热力图")
                        else:
                            self.ax.text(0.5, 0.5, "数据点不足以创建热力图", 
                                   horizontalalignment='center', verticalalignment='center')
                    except ImportError:
                        ax.text(0.5, 0.5, "需要安装seaborn库才能创建热力图", 
                               horizontalalignment='center', verticalalignment='center')
                elif chart_type == "面积图":
                    if use_secondary_y:
                        # 设置双Y轴
                        ax2 = ax.twinx()
                        
                        # 绘制主Y轴
                        ax.fill_between(x_data, y_data, alpha=alpha, color='royalblue', label=chart_info['y_column'])
                        ax.plot(x_data, y_data, color='navy', linewidth=line_width)
                        
                        # 绘制附加Y轴
                        ax2.fill_between(x_data, secondary_y_data, alpha=alpha-0.1, color='green', label=chart_info['secondary_y_column'])
                        ax2.plot(x_data, secondary_y_data, color='darkgreen', linewidth=line_width)
                        
                        # 设置Y轴标签
                        ax.set_ylabel(chart_info['y_column'], fontsize=12)
                        ax2.set_ylabel(chart_info['secondary_y_column'], fontsize=12, color='green')
                        ax2.tick_params(axis='y', colors='green')
                        
                        # 设置图例
                        if self.show_legend_check.isChecked():
                            ax.legend(loc='upper left')
                            ax2.legend(loc='upper right')
                    else:
                        ax.fill_between(x_data, y_data, alpha=alpha, color='royalblue')
                        ax.plot(x_data, y_data, color='navy', linewidth=line_width)
                
                # 设置标题和标签
                ax.set_title(chart_title, fontsize=14, fontweight='bold')
                
                # 设置X轴标签（除非已经设置过）
                if chart_type != "饼图" and chart_type != "箱线图" and chart_type != "直方图":
                    ax.set_xlabel(chart_info['x_column'], fontsize=12)
                
                # 设置网格
                if self.show_grid_check.isChecked() and chart_type != "饼图":
                    ax.grid(True, linestyle='--', alpha=0.7)
                
                # 设置图例（如果尚未设置且需要设置）
                if self.show_legend_check.isChecked() and chart_type not in ["饼图", "箱线图", "直方图", "热力图"] and not use_secondary_y:
                    ax.legend([chart_info['y_column']])
                
                # 紧凑布局
                fig.tight_layout()
                
                # 保存图表
                fig.savefig(file_path, dpi=dpi, bbox_inches='tight')
                
                QMessageBox.information(self, "导出成功", f"图表已成功导出到\n{file_path}")
                
            except Exception as e:
                logger.error(f"导出图表失败: {str(e)}")
                QMessageBox.critical(self, "导出失败", f"导出图表时出错: {str(e)}")
            finally:
                QApplication.restoreOverrideCursor()

    def _on_chart_pick(self, event):
        """处理图表点击事件
        
        Args:
            event: 点击事件对象
        """
        if not hasattr(self, '_current_chart_info'):
            return
            
        chart_info = self._current_chart_info
        chart_type = chart_info['chart_type']
        
        try:
            # 获取点击的数据点索引
            ind = None
            data_index = -1
            
            if chart_type == "柱状图":
                # 柱状图点击处理
                if isinstance(event.artist, matplotlib.patches.Rectangle):
                    # 获取点击的柱子索引
                    bars = event.artist.axes.patches
                    ind = list(bars).index(event.artist)
                    if ind < len(chart_info['row_indices']):
                        data_index = chart_info['row_indices'][ind]
                        
            elif chart_type == "折线图":
                # 折线图点击处理
                if isinstance(event.artist, matplotlib.lines.Line2D):
                    # 找到最近的点
                    ind = event.ind[0]
                    if ind < len(chart_info['row_indices']):
                        data_index = chart_info['row_indices'][ind]
                        
            elif chart_type == "饼图":
                # 饼图点击处理
                if isinstance(event.artist, matplotlib.patches.Wedge):
                    # 获取扇形索引
                    wedges = [w for w in event.artist.axes.patches if isinstance(w, matplotlib.patches.Wedge)]
                    ind = list(wedges).index(event.artist)
                    if ind < len(chart_info['row_indices']):
                        data_index = chart_info['row_indices'][ind]
                        
            elif chart_type == "散点图":
                # 散点图点击处理
                if isinstance(event.artist, matplotlib.collections.PathCollection):
                    # 获取点击的散点索引
                    ind = event.ind[0]  # 取第一个最近的点
                    if ind < len(chart_info['row_indices']):
                        data_index = chart_info['row_indices'][ind]
                        
            elif chart_type == "面积图":
                # 面积图点击处理（通常是线图的一种）
                if isinstance(event.artist, matplotlib.lines.Line2D):
                    # 找到最近的点
                    ind = event.ind[0]
                    if ind < len(chart_info['row_indices']):
                        data_index = chart_info['row_indices'][ind]
            
            # 如果找到了有效的数据索引，显示详细信息
            if data_index >= 0 and data_index < self.model.rowCount():
                self._show_data_point_details(data_index, ind)
                
        except Exception as e:
            logger.error(f"处理图表点击事件时出错: {str(e)}")
            
    def _show_data_point_details(self, row_index, data_index):
        """显示数据点详细信息
        
        Args:
            row_index: 行索引
            data_index: 数据点索引
        """
        if not hasattr(self, '_current_chart_info'):
            return
            
        chart_info = self._current_chart_info
        
        try:
            # 创建详细信息对话框
            dialog = QDialog(self)
            dialog.setWindowTitle("数据点详细信息")
            dialog.setMinimumWidth(400)
            dialog.setMinimumHeight(300)
            
            # 布局
            layout = QVBoxLayout(dialog)
            
            # 添加标题
            title_text = f"数据点 #{data_index+1}"
            if chart_info.get('sample_enabled', False):
                title_text += f" (抽样率 1:{chart_info.get('sample_rate', 1)})"
            title_text += f" (行 #{row_index+1})"
            
            title = QLabel(title_text)
            title.setObjectName("chart_detail_title")
            layout.addWidget(title)
            
            # 创建表格显示所有列数据
            info_table = QTableView()
            info_table.setAlternatingRowColors(True)
            info_table.horizontalHeader().setStretchLastSection(True)
            info_table.verticalHeader().setVisible(False)
            info_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
            
            # 创建模型
            model = QStandardItemModel(0, 2, dialog)
            model.setHorizontalHeaderLabels(["属性", "值"])
            
            # 添加图表相关数据
            model.appendRow([
                QStandardItem("图表类型"),
                QStandardItem(chart_info['chart_type'])
            ])
            
            # 添加X轴数据
            model.appendRow([
                QStandardItem(f"X轴 ({chart_info['x_column']})"),
                QStandardItem(str(chart_info['x_data'][data_index] if data_index < len(chart_info['x_data']) else "N/A"))
            ])
            
            # 添加Y轴数据
            model.appendRow([
                QStandardItem(f"Y轴 ({chart_info['y_column']})"),
                QStandardItem(str(chart_info['y_data'][data_index] if data_index < len(chart_info['y_data']) else "N/A"))
            ])
            
            # 添加附加Y轴数据（如果有）
            if chart_info['secondary_y_column'] and chart_info['secondary_y_data']:
                model.appendRow([
                    QStandardItem(f"附加Y轴 ({chart_info['secondary_y_column']})"),
                    QStandardItem(str(chart_info['secondary_y_data'][data_index] 
                                    if data_index < len(chart_info['secondary_y_data']) else "N/A"))
                ])
            
            # 添加分隔行
            model.appendRow([QStandardItem(""), QStandardItem("")])
            separator_item = model.item(model.rowCount()-1, 0)
            separator_item.setData(15, Qt.SizeHintRole)  # 设置较小的高度
            
            # 添加标题行
            model.appendRow([QStandardItem("所有列数据"), QStandardItem("")])
            title_item = model.item(model.rowCount()-1, 0)
            font = QFont()
            font.setBold(True)
            title_item.setFont(font)
            title_item.setBackground(QColor("#f0f0f0"))
            model.item(model.rowCount()-1, 1).setBackground(QColor("#f0f0f0"))
            
            # 添加该行的所有列数据
            for col in range(self.model.columnCount()):
                col_name = self.model.headerData(col, Qt.Horizontal)
                col_value = self.model.data(self.model.index(row_index, col))
                if col_value is None:
                    col_value = "NULL"
                
                model.appendRow([
                    QStandardItem(str(col_name)),
                    QStandardItem(str(col_value))
                ])
            
            # 设置模型
            info_table.setModel(model)
            
            # 调整列宽
            info_table.resizeColumnsToContents()
            
            # 添加到布局
            layout.addWidget(info_table)
            
            # 添加关闭按钮
            btn_layout = QHBoxLayout()
            close_btn = QPushButton("关闭")
            close_btn.clicked.connect(dialog.accept)
            btn_layout.addStretch()
            btn_layout.addWidget(close_btn)
            layout.addLayout(btn_layout)
            
            # 显示对话框
            dialog.exec_()
            
        except Exception as e:
            logger.error(f"显示数据点详细信息时出错: {str(e)}")
            QMessageBox.warning(self, "错误", f"无法显示详细信息: {str(e)}")
